Graph Visualizer: Replacing Control Rig AST visualizer with common service

#rb halfdan.ingvarsson
#jira na
#preflight https://horde.devtools.epicgames.com/job/614382398169560001036499

#ROBOMERGE-AUTHOR: helge.mathee
#ROBOMERGE-SOURCE: CL 17735754 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v879-17706426)

[CL 17735765 by helge mathee in ue5-release-engine-test branch]
This commit is contained in:
helge mathee
2021-10-06 11:19:48 -04:00
parent b6d019e71c
commit 3f416b5d4d
22 changed files with 2245 additions and 208 deletions

View File

@@ -0,0 +1,360 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "VisualGraph.h"
///////////////////////////////////////////////////////////////////////////////////
/// FVisualGraphSubGraph
///////////////////////////////////////////////////////////////////////////////////
FString FVisualGraphSubGraph::DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const
{
FString EdgeContent;
if(!Nodes.IsEmpty())
{
for(const FVisualGraphEdge& Edge : InGraph->GetEdges())
{
if(Nodes.Contains(Edge.GetSourceNode()) &&
Nodes.Contains(Edge.GetTargetNode()))
{
EdgeContent += Edge.DumpDot(InGraph, InIndendation + 1);
}
}
}
FString SubGraphContent;
for(const FVisualGraphSubGraph& SubGraph : InGraph->GetSubGraphs())
{
if(SubGraph.GetParentGraphIndex() == GetIndex())
{
SubGraphContent += SubGraph.DumpDot(InGraph, InIndendation + 1);
}
}
if(EdgeContent.IsEmpty() && SubGraphContent.IsEmpty())
{
return FString();
}
const FString Indentation1 = DumpDotIndentation(InIndendation);
const FString Indentation2 = DumpDotIndentation(InIndendation + 1);
const FString ColorContent = DumpDotColor(GetColor());
const FString StyleContent = DumpDotStyle(GetStyle());
FString AttributeContent;
if(DisplayName.IsSet())
{
AttributeContent += FString::Printf(TEXT("%slabel = \"%s\";\n"),
*Indentation2, *DisplayName.GetValue().ToString());
}
if(!ColorContent.IsEmpty())
{
AttributeContent += FString::Printf(TEXT("%scolor = %s;\n"),
*Indentation2, *ColorContent);
}
if(!StyleContent.IsEmpty())
{
AttributeContent += FString::Printf(TEXT("%sstyle = %s;\n"),
*Indentation2, *StyleContent);
}
return FString::Printf(TEXT("%ssubgraph cluster_%s {\n%s%s%s%s}\n"),
*Indentation1, *GetName().ToString(),
*AttributeContent,
*EdgeContent,
*SubGraphContent,
*Indentation1
);
}
///////////////////////////////////////////////////////////////////////////////////
/// FVisualGraph
///////////////////////////////////////////////////////////////////////////////////
FVisualGraph::FVisualGraph(const FName& InName, const FName& InDisplayName)
{
Name = InName;
DisplayName = InDisplayName;
Style = EVisualGraphStyle::Filled;
}
int32 FVisualGraph::AddNode(const FName& InName, TOptional<FName> InDisplayName, TOptional<FLinearColor> InColor,
TOptional<EVisualGraphShape> InShape, TOptional<EVisualGraphStyle> InStyle)
{
RefreshNameMapIfNeeded(Nodes, NodeNameMap);
const FName UniqueName = GetUniqueName(InName, NodeNameMap);
FVisualGraphNode Node;
Node.Name = UniqueName;
Node.DisplayName = InDisplayName;
Node.Color = InColor;
Node.Shape = InShape;
Node.Style = InStyle;
Node.SubGraphIndex = INDEX_NONE;
return AddElement(Node, Nodes, NodeNameMap);
}
int32 FVisualGraph::AddEdge(int32 InSourceNode, int32 InTargetNode, EVisualGraphEdgeDirection InDirection,
const FName& InName, TOptional<FName> InDisplayName, TOptional<FLinearColor> InColor,
TOptional<EVisualGraphStyle> InStyle)
{
RefreshNameMapIfNeeded(Edges, EdgeNameMap);
FName DesiredName = InName;
if(DesiredName.IsNone())
{
DesiredName = TEXT("Edge");
}
const FName UniqueName = GetUniqueName(DesiredName, EdgeNameMap);
FVisualGraphEdge Edge;
Edge.Name = UniqueName;
Edge.DisplayName = InDisplayName;
Edge.Color = InColor;
Edge.Style = InStyle;
Edge.SourceNode = InSourceNode;
Edge.TargetNode = InTargetNode;
Edge.Direction = InDirection;
return AddElement(Edge, Edges, EdgeNameMap);
}
int32 FVisualGraph::AddSubGraph(const FName& InName, TOptional<FName> InDisplayName, int32 InParentGraphIndex,
TOptional<FLinearColor> InColor, TOptional<EVisualGraphStyle> InStyle, const TArray<int32> InNodes)
{
RefreshNameMapIfNeeded(SubGraphs, SubGraphNameMap);
const FName UniqueName = GetUniqueName(InName, SubGraphNameMap);
FVisualGraphSubGraph SubGraph;
SubGraph.Name = UniqueName;
SubGraph.DisplayName = InDisplayName;
SubGraph.Color = InColor;
SubGraph.Style = InStyle;
SubGraph.ParentGraphIndex = InParentGraphIndex;
const int32 SubGraphIndex = AddElement(SubGraph, SubGraphs, SubGraphNameMap);
for(const int32 Node : InNodes)
{
AddNodeToSubGraph(Node, SubGraphIndex);
}
return SubGraphIndex;
}
int32 FVisualGraph::FindNode(const FName& InName) const
{
if(const int32* IndexPtr= NodeNameMap.Find(InName))
{
return *IndexPtr;
}
return INDEX_NONE;
}
int32 FVisualGraph::FindEdge(const FName& InName) const
{
if(const int32* IndexPtr= EdgeNameMap.Find(InName))
{
return *IndexPtr;
}
return INDEX_NONE;
}
int32 FVisualGraph::FindSubGraph(const FName& InName) const
{
if(const int32* IndexPtr= SubGraphNameMap.Find(InName))
{
return *IndexPtr;
}
return INDEX_NONE;
}
bool FVisualGraph::AddNodeToSubGraph(int32 InNodeIndex, int32 InSubGraphIndex)
{
if(!Nodes.IsValidIndex(InNodeIndex))
{
return false;
}
if(!SubGraphs.IsValidIndex(InSubGraphIndex))
{
return false;
}
RemoveNodeFromSubGraph(InNodeIndex);
SubGraphs[InSubGraphIndex].Nodes.Add(InNodeIndex);
Nodes[InNodeIndex].SubGraphIndex = InSubGraphIndex;
return true;
}
bool FVisualGraph::RemoveNodeFromSubGraph(int32 InNodeIndex)
{
if(!Nodes.IsValidIndex(InNodeIndex))
{
return false;
}
if(Nodes[InNodeIndex].SubGraphIndex == INDEX_NONE)
{
return false;
}
if(!SubGraphs.IsValidIndex(Nodes[InNodeIndex].SubGraphIndex))
{
return false;
}
SubGraphs[Nodes[InNodeIndex].SubGraphIndex].Nodes.Remove(InNodeIndex);
Nodes[InNodeIndex].SubGraphIndex = INDEX_NONE;
return true;
}
FString FVisualGraph::DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const
{
check(InGraph == this);
const FString Indentation = DumpDotIndentation(InIndendation);
const FString Indentation2 = DumpDotIndentation(InIndendation + 1);
const FString ColorContent = DumpDotColor(GetColor());
const FString StyleContent = DumpDotStyle(GetStyle());
FString AttributeContent;
if(!ColorContent.IsEmpty())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("color = %s"), *ColorContent);
}
if(!StyleContent.IsEmpty())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("style = %s"), *StyleContent);
}
if(!AttributeContent.IsEmpty())
{
AttributeContent = FString::Printf(TEXT("%snode [ %s ];\n"), *Indentation2, *AttributeContent);
}
FString SubGraphContent;
for(const FVisualGraphSubGraph& SubGraph : SubGraphs)
{
if(SubGraph.GetParentGraphIndex() == INDEX_NONE)
{
SubGraphContent += SubGraph.DumpDot(this, InIndendation + 1);
}
}
bool bIsDirectedGraph = true;
FString EdgeContent;
for(const FVisualGraphEdge& Edge : Edges)
{
if(Edge.GetDirection() == EVisualGraphEdgeDirection::BothWays)
{
bIsDirectedGraph = false;
}
const FVisualGraphNode& SourceNode = Nodes[Edge.GetSourceNode()];
const FVisualGraphNode& TargetNode = Nodes[Edge.GetTargetNode()];
// if both source and target node are within the same subgraph
if((SourceNode.SubGraphIndex == TargetNode.SubGraphIndex) &&
(SourceNode.SubGraphIndex != INDEX_NONE))
{
continue;
}
EdgeContent += Edge.DumpDot(this, InIndendation + 1);
}
FString NodeContent;
for(const FVisualGraphNode& Node : Nodes)
{
NodeContent += Node.DumpDot(this, InIndendation + 1);
}
const FString GraphToken = bIsDirectedGraph ? TEXT("digraph") : TEXT("graph");
return FString::Printf(TEXT("%s%s %s {\n%srankdir=\"LR\";\n%s%s%s%s%s}\n"),
*Indentation, *GraphToken, *GetName().ToString(),
*Indentation2,
*AttributeContent,
*SubGraphContent,
*EdgeContent,
*NodeContent,
*Indentation
);
}
bool FVisualGraph::IsNameAvailable(const FName& InName, const TMap<FName, int32>& InMap)
{
return !InMap.Contains(InName);
}
FName FVisualGraph::GetUniqueName(const FName& InName, const TMap<FName, int32>& InMap)
{
const FName DesiredName = InName;
FName Name = DesiredName;
int32 Suffix = 0;
while (!IsNameAvailable(Name, InMap))
{
Suffix++;
Name = *FString::Printf(TEXT("%s_%d"), *DesiredName.ToString(), Suffix);
}
return Name;
}
void FVisualGraph::TransitiveReduction(TFunction<bool(FVisualGraphEdge&)> KeepEdgeFunction)
{
// mark up all valid edges
TArray<int32> ValidEdge;
ValidEdge.AddUninitialized(Nodes.Num() * Nodes.Num());
for(int32 EdgeIndex = 0; EdgeIndex < ValidEdge.Num(); EdgeIndex++)
{
ValidEdge[EdgeIndex] = -1;
}
for(const FVisualGraphEdge& Edge : Edges)
{
const int32 EdgeIndex = Edge.GetSourceNode() * Nodes.Num() + Edge.GetTargetNode();
ValidEdge[EdgeIndex] = Edge.GetIndex();
}
TArray<int32> EdgesToRemove;
// perform transitive reduction on the graph
for(const FVisualGraphNode& NodeA : Nodes)
{
for(const FVisualGraphNode& NodeB : Nodes)
{
for(const FVisualGraphNode& NodeC : Nodes)
{
const int32 EdgeIndexAB = NodeA.GetIndex() * Nodes.Num() + NodeB.GetIndex();
const int32 EdgeIndexBC = NodeB.GetIndex() * Nodes.Num() + NodeC.GetIndex();
const int32 EdgeIndexAC = NodeA.GetIndex() * Nodes.Num() + NodeC.GetIndex();
if(ValidEdge[EdgeIndexAC] != INDEX_NONE &&
ValidEdge[EdgeIndexAB] != INDEX_NONE &&
ValidEdge[EdgeIndexBC] != INDEX_NONE)
{
const int32 EdgeIndex = ValidEdge[EdgeIndexAC];
if(!KeepEdgeFunction(Edges[EdgeIndex]))
{
EdgesToRemove.AddUnique(EdgeIndex);
}
ValidEdge[EdgeIndexAC] = INDEX_NONE;
}
}
}
}
Algo::Sort(EdgesToRemove);
Algo::Reverse(EdgesToRemove);
for(const int32 EdgeToRemove : EdgesToRemove)
{
Edges.RemoveAt(EdgeToRemove);
}
}

View File

@@ -0,0 +1,64 @@
#include "VisualGraphEdge.h"
#include "VisualGraph.h"
///////////////////////////////////////////////////////////////////////////////////
/// FVisualGraphEdge
///////////////////////////////////////////////////////////////////////////////////
FString FVisualGraphEdge::DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const
{
const FString Indentation = DumpDotIndentation(InIndendation);
const FString ColorContent = DumpDotColor(GetColor());
const FString StyleContent = DumpDotStyle(GetStyle());
static const FString DigraphConnector = TEXT("->");
static const FString GraphConnector = TEXT("--");
const FString& ConnectorContent = (GetDirection() == EVisualGraphEdgeDirection::BothWays) ? GraphConnector : DigraphConnector;
FString SourceNodeName = InGraph->GetNodes()[GetSourceNode()].GetName().ToString();
FString TargetNodeName = InGraph->GetNodes()[GetTargetNode()].GetName().ToString();
if(GetDirection() == EVisualGraphEdgeDirection::SourceToTarget)
{
Swap(SourceNodeName, TargetNodeName);
}
FString AttributeContent;
if(DisplayName.IsSet())
{
AttributeContent += FString::Printf(TEXT("label = \"%s\""), *DisplayName.GetValue().ToString());
}
if(Tooltip.IsSet())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("tooltip = \"%s\""), *Tooltip.GetValue());
}
if(!ColorContent.IsEmpty())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("color = %s"), *ColorContent);
}
if(!StyleContent.IsEmpty())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("style = %s"), *StyleContent);
}
if(!AttributeContent.IsEmpty())
{
AttributeContent = FString::Printf(TEXT(" [ %s ]"), *AttributeContent);
}
return FString::Printf(TEXT("%s%s %s %s%s;\n"),
*Indentation, *SourceNodeName, *ConnectorContent, *TargetNodeName, *AttributeContent);
}

View File

@@ -0,0 +1,223 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "VisualGraphElement.h"
#include "VisualGraph.h"
///////////////////////////////////////////////////////////////////////////////////
/// FVisualGraphElement
///////////////////////////////////////////////////////////////////////////////////
FString FVisualGraphElement::DumpDotIndentation(int32 InIndentation)
{
check(InIndentation >= 0);
static TArray<FString> Indentations;
while(Indentations.Num() <= InIndentation)
{
const int32 Indentation = Indentations.Num();
if(Indentation == 0)
{
Indentations.Add(FString());
}
else
{
Indentations.Add(Indentations[Indentation - 1] + TEXT(" "));
}
}
return Indentations[InIndentation];
}
FString FVisualGraphElement::DumpDotColor(const TOptional<FLinearColor>& InColor)
{
if(!InColor.IsSet())
{
return FString();
}
const FLinearColor Color = InColor.GetValue();
static TMap<FName, FLinearColor> ColorScheme;
if(ColorScheme.IsEmpty())
{
ColorScheme.Add(TEXT("alice blue"), FColor::FromHex(TEXT("#F0F8FF")));
ColorScheme.Add(TEXT("antique white"), FColor::FromHex(TEXT("#FAEBD7")));
//ColorScheme.Add(TEXT("aqua"), FColor::FromHex(TEXT("#00FFFF")));
ColorScheme.Add(TEXT("aquamarine"), FColor::FromHex(TEXT("#7FFFD4")));
ColorScheme.Add(TEXT("azure"), FColor::FromHex(TEXT("#F0FFFF")));
ColorScheme.Add(TEXT("beige"), FColor::FromHex(TEXT("#F5F5DC")));
ColorScheme.Add(TEXT("bisque"), FColor::FromHex(TEXT("#FFE4C4")));
ColorScheme.Add(TEXT("black"), FColor::FromHex(TEXT("#000000")));
ColorScheme.Add(TEXT("blanched almond"), FColor::FromHex(TEXT("#FFEBCD")));
ColorScheme.Add(TEXT("blue"), FColor::FromHex(TEXT("#0000FF")));
ColorScheme.Add(TEXT("blue violet"), FColor::FromHex(TEXT("#8A2BE2")));
ColorScheme.Add(TEXT("brown"), FColor::FromHex(TEXT("#A52A2A")));
ColorScheme.Add(TEXT("burlywood"), FColor::FromHex(TEXT("#DEB887")));
ColorScheme.Add(TEXT("cadet blue"), FColor::FromHex(TEXT("#5F9EA0")));
ColorScheme.Add(TEXT("chartreuse"), FColor::FromHex(TEXT("#7FFF00")));
ColorScheme.Add(TEXT("chocolate"), FColor::FromHex(TEXT("#D2691E")));
ColorScheme.Add(TEXT("coral"), FColor::FromHex(TEXT("#FF7F50")));
ColorScheme.Add(TEXT("cornflower blue"), FColor::FromHex(TEXT("#6495ED")));
ColorScheme.Add(TEXT("cornsilk"), FColor::FromHex(TEXT("#FFF8DC")));
ColorScheme.Add(TEXT("crimson"), FColor::FromHex(TEXT("#DC143C")));
ColorScheme.Add(TEXT("cyan"), FColor::FromHex(TEXT("#00FFFF")));
ColorScheme.Add(TEXT("dark blue"), FColor::FromHex(TEXT("#00008B")));
ColorScheme.Add(TEXT("dark cyan"), FColor::FromHex(TEXT("#008B8B")));
ColorScheme.Add(TEXT("dark goldenrod"), FColor::FromHex(TEXT("#B8860B")));
ColorScheme.Add(TEXT("dark gray"), FColor::FromHex(TEXT("#A9A9A9")));
ColorScheme.Add(TEXT("dark green"), FColor::FromHex(TEXT("#006400")));
ColorScheme.Add(TEXT("dark khaki"), FColor::FromHex(TEXT("#BDB76B")));
ColorScheme.Add(TEXT("dark magenta"), FColor::FromHex(TEXT("#8B008B")));
ColorScheme.Add(TEXT("dark olive green"), FColor::FromHex(TEXT("#556B2F")));
ColorScheme.Add(TEXT("dark orange"), FColor::FromHex(TEXT("#FF8C00")));
ColorScheme.Add(TEXT("dark orchid"), FColor::FromHex(TEXT("#9932CC")));
ColorScheme.Add(TEXT("dark red"), FColor::FromHex(TEXT("#8B0000")));
ColorScheme.Add(TEXT("dark salmon"), FColor::FromHex(TEXT("#E9967A")));
ColorScheme.Add(TEXT("dark sea green"), FColor::FromHex(TEXT("#8FBC8F")));
ColorScheme.Add(TEXT("dark slate blue"), FColor::FromHex(TEXT("#483D8B")));
ColorScheme.Add(TEXT("dark slate gray"), FColor::FromHex(TEXT("#2F4F4F")));
ColorScheme.Add(TEXT("dark turquoise"), FColor::FromHex(TEXT("#00CED1")));
ColorScheme.Add(TEXT("dark violet"), FColor::FromHex(TEXT("#9400D3")));
ColorScheme.Add(TEXT("deep pink"), FColor::FromHex(TEXT("#FF1493")));
ColorScheme.Add(TEXT("deep sky blue"), FColor::FromHex(TEXT("#00BFFF")));
ColorScheme.Add(TEXT("dim gray"), FColor::FromHex(TEXT("#696969")));
ColorScheme.Add(TEXT("dodger blue"), FColor::FromHex(TEXT("#1E90FF")));
ColorScheme.Add(TEXT("firebrick"), FColor::FromHex(TEXT("#B22222")));
ColorScheme.Add(TEXT("floral white"), FColor::FromHex(TEXT("#FFFAF0")));
ColorScheme.Add(TEXT("forest green"), FColor::FromHex(TEXT("#228B22")));
ColorScheme.Add(TEXT("fuchsia"), FColor::FromHex(TEXT("#FF00FF")));
ColorScheme.Add(TEXT("gainsboro"), FColor::FromHex(TEXT("#DCDCDC")));
ColorScheme.Add(TEXT("ghost white"), FColor::FromHex(TEXT("#F8F8FF")));
ColorScheme.Add(TEXT("gold"), FColor::FromHex(TEXT("#FFD700")));
ColorScheme.Add(TEXT("goldenrod"), FColor::FromHex(TEXT("#DAA520")));
ColorScheme.Add(TEXT("gray"), FColor::FromHex(TEXT("#BEBEBE")));
ColorScheme.Add(TEXT("web gray"), FColor::FromHex(TEXT("#808080")));
ColorScheme.Add(TEXT("green"), FColor::FromHex(TEXT("#00FF00")));
ColorScheme.Add(TEXT("web green"), FColor::FromHex(TEXT("#008000")));
ColorScheme.Add(TEXT("green yellow"), FColor::FromHex(TEXT("#ADFF2F")));
ColorScheme.Add(TEXT("honeydew"), FColor::FromHex(TEXT("#F0FFF0")));
ColorScheme.Add(TEXT("hot pink"), FColor::FromHex(TEXT("#FF69B4")));
ColorScheme.Add(TEXT("indian red"), FColor::FromHex(TEXT("#CD5C5C")));
ColorScheme.Add(TEXT("indigo"), FColor::FromHex(TEXT("#4B0082")));
ColorScheme.Add(TEXT("ivory"), FColor::FromHex(TEXT("#FFFFF0")));
ColorScheme.Add(TEXT("khaki"), FColor::FromHex(TEXT("#F0E68C")));
ColorScheme.Add(TEXT("lavender"), FColor::FromHex(TEXT("#E6E6FA")));
ColorScheme.Add(TEXT("lavender blush"), FColor::FromHex(TEXT("#FFF0F5")));
ColorScheme.Add(TEXT("lawn green"), FColor::FromHex(TEXT("#7CFC00")));
ColorScheme.Add(TEXT("lemon chiffon"), FColor::FromHex(TEXT("#FFFACD")));
ColorScheme.Add(TEXT("light blue"), FColor::FromHex(TEXT("#ADD8E6")));
ColorScheme.Add(TEXT("light coral"), FColor::FromHex(TEXT("#F08080")));
ColorScheme.Add(TEXT("light cyan"), FColor::FromHex(TEXT("#E0FFFF")));
ColorScheme.Add(TEXT("light goldenrod"), FColor::FromHex(TEXT("#FAFAD2")));
ColorScheme.Add(TEXT("light gray"), FColor::FromHex(TEXT("#D3D3D3")));
ColorScheme.Add(TEXT("light green"), FColor::FromHex(TEXT("#90EE90")));
ColorScheme.Add(TEXT("light pink"), FColor::FromHex(TEXT("#FFB6C1")));
ColorScheme.Add(TEXT("light salmon"), FColor::FromHex(TEXT("#FFA07A")));
ColorScheme.Add(TEXT("light sea green"), FColor::FromHex(TEXT("#20B2AA")));
ColorScheme.Add(TEXT("light sky blue"), FColor::FromHex(TEXT("#87CEFA")));
ColorScheme.Add(TEXT("light slate gray"), FColor::FromHex(TEXT("#778899")));
ColorScheme.Add(TEXT("light steel blue"), FColor::FromHex(TEXT("#B0C4DE")));
ColorScheme.Add(TEXT("light yellow"), FColor::FromHex(TEXT("#FFFFE0")));
ColorScheme.Add(TEXT("lime"), FColor::FromHex(TEXT("#00FF00")));
ColorScheme.Add(TEXT("lime green"), FColor::FromHex(TEXT("#32CD32")));
ColorScheme.Add(TEXT("linen"), FColor::FromHex(TEXT("#FAF0E6")));
ColorScheme.Add(TEXT("magenta"), FColor::FromHex(TEXT("#FF00FF")));
ColorScheme.Add(TEXT("maroon"), FColor::FromHex(TEXT("#B03060")));
ColorScheme.Add(TEXT("web maroon"), FColor::FromHex(TEXT("#800000")));
ColorScheme.Add(TEXT("medium aquamarine"), FColor::FromHex(TEXT("#66CDAA")));
ColorScheme.Add(TEXT("medium blue"), FColor::FromHex(TEXT("#0000CD")));
ColorScheme.Add(TEXT("medium orchid"), FColor::FromHex(TEXT("#BA55D3")));
ColorScheme.Add(TEXT("medium purple"), FColor::FromHex(TEXT("#9370DB")));
ColorScheme.Add(TEXT("medium sea green"), FColor::FromHex(TEXT("#3CB371")));
ColorScheme.Add(TEXT("medium slate blue"), FColor::FromHex(TEXT("#7B68EE")));
ColorScheme.Add(TEXT("medium spring green"), FColor::FromHex(TEXT("#00FA9A")));
ColorScheme.Add(TEXT("medium turquoise"), FColor::FromHex(TEXT("#48D1CC")));
ColorScheme.Add(TEXT("medium violet red"), FColor::FromHex(TEXT("#C71585")));
ColorScheme.Add(TEXT("midnight blue"), FColor::FromHex(TEXT("#191970")));
ColorScheme.Add(TEXT("mint cream"), FColor::FromHex(TEXT("#F5FFFA")));
ColorScheme.Add(TEXT("misty rose"), FColor::FromHex(TEXT("#FFE4E1")));
ColorScheme.Add(TEXT("moccasin"), FColor::FromHex(TEXT("#FFE4B5")));
ColorScheme.Add(TEXT("navajo white"), FColor::FromHex(TEXT("#FFDEAD")));
ColorScheme.Add(TEXT("navy blue"), FColor::FromHex(TEXT("#000080")));
ColorScheme.Add(TEXT("old lace"), FColor::FromHex(TEXT("#FDF5E6")));
ColorScheme.Add(TEXT("olive"), FColor::FromHex(TEXT("#808000")));
ColorScheme.Add(TEXT("olive drab"), FColor::FromHex(TEXT("#6B8E23")));
ColorScheme.Add(TEXT("orange"), FColor::FromHex(TEXT("#FFA500")));
ColorScheme.Add(TEXT("orange red"), FColor::FromHex(TEXT("#FF4500")));
ColorScheme.Add(TEXT("orchid"), FColor::FromHex(TEXT("#DA70D6")));
ColorScheme.Add(TEXT("pale goldenrod"), FColor::FromHex(TEXT("#EEE8AA")));
ColorScheme.Add(TEXT("pale green"), FColor::FromHex(TEXT("#98FB98")));
ColorScheme.Add(TEXT("pale turquoise"), FColor::FromHex(TEXT("#AFEEEE")));
ColorScheme.Add(TEXT("pale violet red"), FColor::FromHex(TEXT("#DB7093")));
ColorScheme.Add(TEXT("papaya whip"), FColor::FromHex(TEXT("#FFEFD5")));
ColorScheme.Add(TEXT("peach puff"), FColor::FromHex(TEXT("#FFDAB9")));
ColorScheme.Add(TEXT("peru"), FColor::FromHex(TEXT("#CD853F")));
ColorScheme.Add(TEXT("pink"), FColor::FromHex(TEXT("#FFC0CB")));
ColorScheme.Add(TEXT("plum"), FColor::FromHex(TEXT("#DDA0DD")));
ColorScheme.Add(TEXT("powder blue"), FColor::FromHex(TEXT("#B0E0E6")));
ColorScheme.Add(TEXT("purple"), FColor::FromHex(TEXT("#A020F0")));
ColorScheme.Add(TEXT("web purple"), FColor::FromHex(TEXT("#800080")));
ColorScheme.Add(TEXT("rebecca purple"), FColor::FromHex(TEXT("#663399")));
ColorScheme.Add(TEXT("red"), FColor::FromHex(TEXT("#FF0000")));
ColorScheme.Add(TEXT("rosy brown"), FColor::FromHex(TEXT("#BC8F8F")));
ColorScheme.Add(TEXT("royal blue"), FColor::FromHex(TEXT("#4169E1")));
ColorScheme.Add(TEXT("saddle brown"), FColor::FromHex(TEXT("#8B4513")));
ColorScheme.Add(TEXT("salmon"), FColor::FromHex(TEXT("#FA8072")));
ColorScheme.Add(TEXT("sandy brown"), FColor::FromHex(TEXT("#F4A460")));
ColorScheme.Add(TEXT("sea green"), FColor::FromHex(TEXT("#2E8B57")));
ColorScheme.Add(TEXT("seashell"), FColor::FromHex(TEXT("#FFF5EE")));
ColorScheme.Add(TEXT("sienna"), FColor::FromHex(TEXT("#A0522D")));
ColorScheme.Add(TEXT("silver"), FColor::FromHex(TEXT("#C0C0C0")));
ColorScheme.Add(TEXT("sky blue"), FColor::FromHex(TEXT("#87CEEB")));
ColorScheme.Add(TEXT("slate blue"), FColor::FromHex(TEXT("#6A5ACD")));
ColorScheme.Add(TEXT("slate gray"), FColor::FromHex(TEXT("#708090")));
ColorScheme.Add(TEXT("snow"), FColor::FromHex(TEXT("#FFFAFA")));
ColorScheme.Add(TEXT("spring green"), FColor::FromHex(TEXT("#00FF7F")));
ColorScheme.Add(TEXT("steel blue"), FColor::FromHex(TEXT("#4682B4")));
ColorScheme.Add(TEXT("tan"), FColor::FromHex(TEXT("#D2B48C")));
ColorScheme.Add(TEXT("teal"), FColor::FromHex(TEXT("#008080")));
ColorScheme.Add(TEXT("thistle"), FColor::FromHex(TEXT("#D8BFD8")));
ColorScheme.Add(TEXT("tomato"), FColor::FromHex(TEXT("#FF6347")));
ColorScheme.Add(TEXT("turquoise"), FColor::FromHex(TEXT("#40E0D0")));
ColorScheme.Add(TEXT("violet"), FColor::FromHex(TEXT("#EE82EE")));
ColorScheme.Add(TEXT("wheat"), FColor::FromHex(TEXT("#F5DEB3")));
ColorScheme.Add(TEXT("white"), FColor::FromHex(TEXT("#FFFFFF")));
ColorScheme.Add(TEXT("white smoke"), FColor::FromHex(TEXT("#F5F5F5")));
ColorScheme.Add(TEXT("yellow"), FColor::FromHex(TEXT("#FFFF00")));
ColorScheme.Add(TEXT("yellow green"), FColor::FromHex(TEXT("#9ACD32")));
}
FName SchemeName = NAME_None;
float MinDelta = FLT_MAX;
for(const TPair<FName, FLinearColor>& Pair : ColorScheme)
{
const float Delta = FLinearColor::Dist(Pair.Value, Color);
if(MinDelta > Delta)
{
MinDelta = Delta;
SchemeName = Pair.Key;
}
}
FString SchemeString = SchemeName.ToString();
SchemeString.RemoveSpacesInline();
return SchemeString;
}
FString FVisualGraphElement::DumpDotShape(const TOptional<EVisualGraphShape>& InShape)
{
if(!InShape.IsSet())
{
return FString();
}
return StaticEnum<EVisualGraphShape>()->GetDisplayNameTextByValue((int64)InShape.GetValue()).ToString().ToLower();
}
FString FVisualGraphElement::DumpDotStyle(const TOptional<EVisualGraphStyle>& InStyle)
{
if(!InStyle.IsSet())
{
return FString();
}
return StaticEnum<EVisualGraphStyle>()->GetDisplayNameTextByValue((int64)InStyle.GetValue()).ToString().ToLower();
}

View File

@@ -0,0 +1,75 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "VisualGraphNode.h"
#include "VisualGraph.h"
///////////////////////////////////////////////////////////////////////////////////
/// FVisualGraphNode
///////////////////////////////////////////////////////////////////////////////////
FString FVisualGraphNode::DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const
{
const FString Indentation = DumpDotIndentation(InIndendation);
const FString StyleContent = DumpDotStyle(GetStyle());
const FString ShapeContent = DumpDotShape(GetShape());
const FString ColorContent = DumpDotColor(GetColor());
FString AttributeContent;
if(DisplayName.IsSet())
{
AttributeContent += FString::Printf(TEXT("label = \"%s\""), *DisplayName.GetValue().ToString());
}
if(Tooltip.IsSet())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("tooltip = \"%s\""), *Tooltip.GetValue());
}
if(!StyleContent.IsEmpty())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("style = %s"), *StyleContent);
}
if(!ShapeContent.IsEmpty())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("shape = %s"), *ShapeContent);
}
if(!ColorContent.IsEmpty())
{
if(!AttributeContent.IsEmpty())
{
AttributeContent += TEXT(", ");
}
AttributeContent += FString::Printf(TEXT("color = %s"), *ColorContent);
if(GetColor().IsSet())
{
if(!GetStyle().IsSet() || GetStyle().GetValue() == EVisualGraphStyle::Filled)
{
const FLinearColor CurrentColor = GetColor().GetValue();
const float Brightness = (0.299f * CurrentColor.R + 0.587f * CurrentColor.G + 0.114f * CurrentColor.B);
if(Brightness < 0.5f)
{
AttributeContent += TEXT(", fontcolor = white");
}
}
}
}
if(!AttributeContent.IsEmpty())
{
AttributeContent = FString::Printf(TEXT(" [ %s ]"), *AttributeContent);
}
return FString::Printf(TEXT("%s%s%s;\n"), *Indentation, *GetName().ToString(), *AttributeContent);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "VisualGraphUtilsModule.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_MODULE(FDefaultModuleImpl, VisualGraphUtils);
DEFINE_LOG_CATEGORY(LogVisualGraphUtils);

View File

@@ -0,0 +1,135 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "VisualGraphElement.h"
#include "VisualGraphNode.h"
#include "VisualGraphEdge.h"
class FVisualGraph;
class VISUALGRAPHUTILS_API FVisualGraphSubGraph : public FVisualGraphElement
{
public:
FVisualGraphSubGraph()
: FVisualGraphElement()
, ParentGraphIndex(INDEX_NONE)
{}
virtual ~FVisualGraphSubGraph() override {}
int32 GetParentGraphIndex() const { return ParentGraphIndex; }
const TArray<int32>& GetNodes() const { return Nodes; }
protected:
virtual FString DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const override;
int32 ParentGraphIndex;
TArray<int32> Nodes;
friend class FVisualGraph;
};
class VISUALGRAPHUTILS_API FVisualGraph : public FVisualGraphElement
{
public:
FVisualGraph()
: FVisualGraphElement()
{}
virtual ~FVisualGraph() override {}
FVisualGraph(const FName& InName, const FName& InDisplayName = NAME_None);
const TArray<FVisualGraphNode>& GetNodes() const { return Nodes; }
const TArray<FVisualGraphEdge>& GetEdges() const { return Edges; }
const TArray<FVisualGraphSubGraph>& GetSubGraphs() const { return SubGraphs; }
int32 AddNode(
const FName& InName,
TOptional<FName> InDisplayName = TOptional<FName>(),
TOptional<FLinearColor> InColor = TOptional<FLinearColor>(),
TOptional<EVisualGraphShape> InShape = TOptional<EVisualGraphShape>(),
TOptional<EVisualGraphStyle> InStyle = TOptional<EVisualGraphStyle>());
int32 AddEdge(
int32 InSourceNode,
int32 InTargetNode,
EVisualGraphEdgeDirection InDirection,
const FName& InName = NAME_None,
TOptional<FName> InDisplayName = TOptional<FName>(),
TOptional<FLinearColor> InColor = TOptional<FLinearColor>(),
TOptional<EVisualGraphStyle> InStyle = TOptional<EVisualGraphStyle>());
int32 AddSubGraph(
const FName& InName,
TOptional<FName> InDisplayName = TOptional<FName>(),
int32 InParentGraphIndex = INDEX_NONE,
TOptional<FLinearColor> InColor = TOptional<FLinearColor>(),
TOptional<EVisualGraphStyle> InStyle = TOptional<EVisualGraphStyle>(),
const TArray<int32> InNodes = TArray<int32>());
int32 FindNode(const FName& InName) const;
int32 FindEdge(const FName& InName) const;
int32 FindSubGraph(const FName& InName) const;
bool AddNodeToSubGraph(int32 InNodeIndex, int32 InSubGraphIndex);
bool RemoveNodeFromSubGraph(int32 InNodeIndex);
void TransitiveReduction(TFunction<bool(FVisualGraphEdge&)> KeepEdgeFunction);
FORCEINLINE FString DumpDot() const
{
return DumpDot(this, 0);
}
protected:
virtual FString DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const override;
template<typename T>
FORCEINLINE static void RefreshNameMap(const TArray<T>& InElements, TMap<FName, int32>& OutMap)
{
OutMap.Reset();
for(const T& Element: InElements)
{
OutMap.Add(Element.Name, Element.Index);
}
}
template<typename T>
FORCEINLINE static void RefreshNameMapIfNeeded(const TArray<T>& InElements, TMap<FName, int32>& OutMap)
{
if(OutMap.Num() == InElements.Num())
{
return;
}
RefreshNameMap(InElements, OutMap);
}
template<typename T>
FORCEINLINE static int32 AddElement(const T& InElement, TArray<T>& OutElements, TMap<FName, int32>& OutMap)
{
const int32 AddedIndex = OutElements.Add(InElement);
if(OutElements.IsValidIndex(AddedIndex))
{
OutElements[AddedIndex].Index = AddedIndex;
OutMap.Add(InElement.Name, AddedIndex);
}
return AddedIndex;
}
static bool IsNameAvailable(const FName& InName, const TMap<FName, int32>& InMap);
static FName GetUniqueName(const FName& InName, const TMap<FName, int32>& InMap);
TArray<FVisualGraphNode> Nodes;
TArray<FVisualGraphEdge> Edges;
TArray<FVisualGraphSubGraph> SubGraphs;
TMap<FName,int32> NodeNameMap;
TMap<FName,int32> EdgeNameMap;
TMap<FName,int32> SubGraphNameMap;
};

View File

@@ -0,0 +1,42 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "VisualGraphElement.h"
#include "VisualGraphEdge.generated.h"
UENUM()
enum class EVisualGraphEdgeDirection : uint8
{
SourceToTarget,
TargetToSource,
BothWays
};
class VISUALGRAPHUTILS_API FVisualGraphEdge : public FVisualGraphElement
{
public:
FVisualGraphEdge()
: FVisualGraphElement()
, Direction(EVisualGraphEdgeDirection::SourceToTarget)
{}
virtual ~FVisualGraphEdge() override {}
int32 GetSourceNode() const { return SourceNode; }
int32 GetTargetNode() const { return TargetNode; }
EVisualGraphEdgeDirection GetDirection() const { return Direction; }
protected:
virtual FString DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const override;
int32 SourceNode;
int32 TargetNode;
EVisualGraphEdgeDirection Direction;
friend class FVisualGraph;
friend class FVisualGraphSubGraph;
};

View File

@@ -0,0 +1,77 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "VisualGraphElement.generated.h"
class FVisualGraph;
UENUM()
enum class EVisualGraphShape : uint8
{
Box,
Polygon,
Ellipse,
Circle,
Triangle,
PlainText,
Diamond,
Parallelogram,
House
};
UENUM()
enum class EVisualGraphStyle : uint8
{
Filled,
Diagonals,
Rounded,
Dashed,
Dotted,
Solid,
Bold
};
class VISUALGRAPHUTILS_API FVisualGraphElement
{
public:
FVisualGraphElement()
: Name(NAME_None)
, DisplayName()
, Index(INDEX_NONE)
, Color()
, Style()
{}
virtual ~FVisualGraphElement() {}
FName GetName() const { return Name; }
FName GetDisplayName() const { return DisplayName.IsSet() ? DisplayName.GetValue() : Name; }
TOptional<FString> GetTooltip() const { return Tooltip; }
void SetTooltip(const FString& InTooltip) const { Tooltip = InTooltip; }
int32 GetIndex() const { return Index; }
TOptional<FLinearColor> GetColor() const { return Color; }
void SetColor(FLinearColor InValue) { Color = InValue; }
TOptional<EVisualGraphStyle> GetStyle() const { return Style; }
void SetStyle(EVisualGraphStyle InValue) { Style = InValue; }
protected:
virtual FString DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const = 0;
static FString DumpDotIndentation(int32 InIndentation);
static FString DumpDotColor(const TOptional<FLinearColor>& InColor);
static FString DumpDotShape(const TOptional<EVisualGraphShape>& InShape);
static FString DumpDotStyle(const TOptional<EVisualGraphStyle>& InStyle);
FName Name;
TOptional<FName> DisplayName;
mutable TOptional<FString> Tooltip;
int32 Index;
TOptional<FLinearColor> Color;
TOptional<EVisualGraphStyle> Style;
friend class FVisualGraph;
};

View File

@@ -0,0 +1,31 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "VisualGraphElement.h"
class VISUALGRAPHUTILS_API FVisualGraphNode : public FVisualGraphElement
{
public:
FVisualGraphNode()
: FVisualGraphElement()
, Shape()
, SubGraphIndex(INDEX_NONE)
{}
virtual ~FVisualGraphNode() override {}
TOptional<EVisualGraphShape> GetShape() const { return Shape; }
void SetShape(EVisualGraphShape InValue) { Shape = InValue; }
protected:
virtual FString DumpDot(const FVisualGraph* InGraph, int32 InIndendation) const override;
TOptional<EVisualGraphShape> Shape;
int32 SubGraphIndex;
friend class FVisualGraph;
friend class FVisualGraphSubGraph;
};

View File

@@ -0,0 +1,25 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "VisualGraph.h"
class VISUALGRAPHUTILS_API FVisualGraphObjectUtils
{
public:
static FVisualGraph TraverseUObjectReferences(
const TArray<UObject*>& InObjects,
const TArray<UClass*>& InClassesToSkip = TArray<UClass*>(),
const TArray<UObject*>& InOutersToSkip = TArray<UObject*>(),
const TArray<UObject*>& InOutersToUse = TArray<UObject*>(),
bool bTraverseObjectsInOuter = true,
bool bCollectReferencesBySerialize = true,
bool bRecursive = true
);
static FVisualGraph TraverseTickOrder(
const TArray<UObject*>& InObjects
);
};

View File

@@ -0,0 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "VisualGraphNode.h"
#include "VisualGraphEdge.h"
#include "VisualGraph.h"

View File

@@ -0,0 +1,8 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
DECLARE_LOG_CATEGORY_EXTERN(LogVisualGraphUtils, Log, All);

View File

@@ -0,0 +1,27 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class VisualGraphUtils : ModuleRules
{
public VisualGraphUtils(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(
new string[] {
"Core",
"CoreUObject",
"Engine",
}
);
if (Target.bBuildEditor == true)
{
PublicDependencyModuleNames.AddRange(
new string[]
{
"ApplicationCore",
}
);
}
}
}