You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden ========================== MAJOR FEATURES + CHANGES ========================== Change 2927181 on 2016/03/29 by Dmitry.Rekman (Optionally) exclude idle time from server FPS charts. - Time spent waiting for the next frame in order to hit capped FPS can be optionally excluded by using t.FPSChart.ExcludeIdleTime (set to 1 for servers). - Server FPS charts analytics events and log output will include the information if idle time was excluded. - Also: added a log line each time we detect a server hitch for easier pin-pointing them in the log. #rb Paul.Moore #codereview Paul.Moore, Michael.Noland #tests Ran Linux server and Windows client on compatible content. Change 2927084 on 2016/03/29 by Ben.Marsh BuildGraph: Don't allow triggers to run until all their order dependencies are complete. Just because a downstream node doesn't have a dependency on an upstream node via temp storage doesn't mean it can run immediately. #rb none #tests none Change2927060on 2016/03/29 by Michael.Noland Renamed GPU analytics event from GPU to DesktopGPU to reflect that it is the default desktop adapter and not the one we initialized (which is GPUAdapter) Updated text/log based FPS chart events to print out GPUAdapter instead (with DesktopGPU in parens if they differ, e.g., in an optimus setup) #rb marcus.wassmer #tests Ran and did some fps charts Change 2927048 on 2016/03/29 by Michael.Noland HLOD: Removed an unused cvar r.HLODEnabled (everything is done thru r.HLOD) #tests Compiled and ran Paragon #rb marcus.wassmer Change 2926920 on 2016/03/29 by Ben.Marsh BuildGraph: Update schema with Rename task. Change 2926911 on 2016/03/29 by Ben.Marsh BuildGraph: Add a task which can rename files matching a given wildcard. Syntax is: <Rename Files="*.txt" To="*.md"> or <Rename Files="Engine/Build/..." From="*.txt" To="*.md"/> #rb none #tests none Change2926908on 2016/03/29 by Andrew.Grant Fix for CDO properties of renamed blueprints not being applied #rb none #tests loaded Origin map (renamed from Playgo3) and verified properties are applied. Change 2926799 on 2016/03/29 by Jason.Bestimt #ORION_DG - Merge MAIN (23) @ CL# 2926780 #RB:none #Tests:none Change 2926663 on 2016/03/29 by david.nikdel #ROBOMERGE-OBO: jason.bestimt #ROBOMERGE-SOURCE: CL 2926660 in //Orion/Release-0.23/... via CL 2926662 #ROBOMERGE-BOT: ORION (Main -> Dev-General) #ORION_23 - Potential fix for Cook failures "Fix shelved in 2926635, tested in Dev-Blueprints. Could not run any GEditor related logic safely in ShutdownModule because of the same destruction issue orders that caused the bug in the first place. I will chat with Editor team about nulling out GEditor the same way we null out GUnrealEd." #RB:none #Tests: none [CodeReviewed]: andrew.grant, dan.oconnor Change 2926510 on 2016/03/29 by Andrew.Grant Potential fix for OR-18207 - editor becomes unresponsive (audio deadlock) #rb none #tests compiled Change 2926495 on 2016/03/29 by Rob.Cannaday Change storing HTTP requests as raw pointers to weak pointers with validity being checked via Pinning it #jira FORT-18947 #jira OR-17695 #tests golden path #rb eric.newman Change 2926427 on 2016/03/29 by Josh.Markiewicz #UE4 - fixed typo #rb none #tests none Change 2926250 on 2016/03/29 by Martin.Mittring fixed OR-18489 HERO: IGGY: RMB on E ability causes blinding hair effect #rb:Chris.Bunner #codereview:Brian.Karis Change 2926224 on 2016/03/29 by Daniel.Lamb Fix for potenital threading issue with Console manager removing vars which could cause double free. #rb Robert.Manuszewski #test Orion cook Change 2926174 on 2016/03/29 by Gareth.Martin Cloned fix for bUseMaterialPositionOffsetInStaticLighting crashing across from //UE4/Dev-Landscape/ to unblock people #rb #tests editor Change 2925968 on 2016/03/29 by David.Nikdel #MCP #OSS - Read RedirectUrl from ini #RB: Eric.Newman #TESTS: compiled in another branch (merge over) #ROBOMERGE: Main [CL 2929424 by Andrew Grant in Main branch]
444 lines
15 KiB
C#
444 lines
15 KiB
C#
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.IO;
|
|
using AutomationTool;
|
|
using UnrealBuildTool;
|
|
using System.Reflection;
|
|
using System.Xml;
|
|
using System.Linq;
|
|
using System.Diagnostics;
|
|
|
|
namespace AutomationTool
|
|
{
|
|
/// <summary>
|
|
/// Options for how the graph should be printed
|
|
/// </summary>
|
|
enum GraphPrintOptions
|
|
{
|
|
/// <summary>
|
|
/// Includes the list of dependencies for each node
|
|
/// </summary>
|
|
ShowDependencies = 0x1,
|
|
|
|
/// <summary>
|
|
/// Includes the list of notifiers for each node
|
|
/// </summary>
|
|
ShowNotifications = 0x2,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Diagnostic message from the graph script. These messages are parsed at startup, then culled along with the rest of the graph nodes before output. Doing so
|
|
/// allows errors and warnings which are only output if a node is part of the graph being executed.
|
|
/// </summary>
|
|
class GraphDiagnostic
|
|
{
|
|
/// <summary>
|
|
/// The diagnostic event type
|
|
/// </summary>
|
|
public LogEventType EventType;
|
|
|
|
/// <summary>
|
|
/// The message to display
|
|
/// </summary>
|
|
public string Message;
|
|
|
|
/// <summary>
|
|
/// The node which this diagnostic is declared in. If the node is culled from the graph, the message will not be displayed.
|
|
/// </summary>
|
|
public Node EnclosingNode;
|
|
|
|
/// <summary>
|
|
/// The agent group that this diagnostic is declared in. If the entire group is culled from the graph, the message will not be displayed.
|
|
/// </summary>
|
|
public AgentGroup EnclosingGroup;
|
|
|
|
/// <summary>
|
|
/// The trigger that this diagnostic is declared in. If this trigger is not being run, the message will not be displayed.
|
|
/// </summary>
|
|
public ManualTrigger EnclosingTrigger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Definition of a graph.
|
|
/// </summary>
|
|
class Graph
|
|
{
|
|
/// <summary>
|
|
/// List of nodes grouped by the agent they will be executed on
|
|
/// </summary>
|
|
public List<AgentGroup> Groups = new List<AgentGroup>();
|
|
|
|
/// <summary>
|
|
/// All manual triggers that are part of this graph
|
|
/// </summary>
|
|
public Dictionary<string, ManualTrigger> NameToTrigger = new Dictionary<string, ManualTrigger>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of names to the corresponding node.
|
|
/// </summary>
|
|
public Dictionary<string, Node> NameToNode = new Dictionary<string,Node>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of names to their corresponding node output.
|
|
/// </summary>
|
|
public Dictionary<string, NodeOutput> NameToNodeOutput = new Dictionary<string,NodeOutput>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Mapping of aggregate names to their respective nodes
|
|
/// </summary>
|
|
public Dictionary<string, Node[]> AggregateNameToNodes = new Dictionary<string,Node[]>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Diagnostic messages for this graph
|
|
/// </summary>
|
|
public List<GraphDiagnostic> Diagnostics = new List<GraphDiagnostic>();
|
|
|
|
/// <summary>
|
|
/// Default constructor
|
|
/// </summary>
|
|
public Graph()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether a given name (or tag name) already exists
|
|
/// </summary>
|
|
/// <param name="Name">The name to check. May begin with a '#' character.</param>
|
|
/// <returns>True if the name exists, false otherwise.</returns>
|
|
public bool ContainsName(string Name)
|
|
{
|
|
// Strip off the tag character, if present
|
|
string TrimName = Name.StartsWith("#")? Name.Substring(1) : Name;
|
|
|
|
// Check it doesn't match anything in the graph
|
|
return NameToNode.ContainsKey(TrimName) || NameToNodeOutput.ContainsKey(TrimName) || AggregateNameToNodes.ContainsKey(TrimName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to resolve the given name to one or more nodes. Checks for aggregates, and actual nodes.
|
|
/// </summary>
|
|
/// <param name="Name">The name to search for</param>
|
|
/// <param name="OutNodes">If the name is a match, receives an array of nodes and their output names</param>
|
|
/// <returns>True if the name was found, false otherwise.</returns>
|
|
public bool TryResolveReference(string Name, out Node[] OutNodes)
|
|
{
|
|
// Check if it's a tag reference or node reference
|
|
if(Name.StartsWith("#"))
|
|
{
|
|
// Check if it's a regular node or output name
|
|
NodeOutput Output;
|
|
if(NameToNodeOutput.TryGetValue(Name.Substring(1), out Output))
|
|
{
|
|
OutNodes = new Node[]{ Output.ProducingNode };
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check if it's a regular node or output name
|
|
Node Node;
|
|
if(NameToNode.TryGetValue(Name, out Node))
|
|
{
|
|
OutNodes = new Node[]{ Node };
|
|
return true;
|
|
}
|
|
|
|
// Check if it's an aggregate name
|
|
Node[] Nodes;
|
|
if(AggregateNameToNodes.TryGetValue(Name, out Nodes))
|
|
{
|
|
OutNodes = Nodes;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Otherwise fail
|
|
OutNodes = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to resolve the given name to one or more node outputs. Checks for aggregates, and actual nodes.
|
|
/// </summary>
|
|
/// <param name="Name">The name to search for</param>
|
|
/// <param name="OutOutputs">If the name is a match, receives an array of nodes and their output names</param>
|
|
/// <returns>True if the name was found, false otherwise.</returns>
|
|
public bool TryResolveInputReference(string Name, out NodeOutput[] OutOutputs)
|
|
{
|
|
// Check if it's a tag reference or node reference
|
|
if(Name.StartsWith("#"))
|
|
{
|
|
// Check if it's a regular node or output name
|
|
NodeOutput Output;
|
|
if(NameToNodeOutput.TryGetValue(Name.Substring(1), out Output))
|
|
{
|
|
OutOutputs = new NodeOutput[]{ Output };
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check if it's a regular node or output name
|
|
Node Node;
|
|
if(NameToNode.TryGetValue(Name, out Node))
|
|
{
|
|
OutOutputs = Node.Outputs.Union(Node.Inputs).ToArray();
|
|
return true;
|
|
}
|
|
|
|
// Check if it's an aggregate name
|
|
Node[] Nodes;
|
|
if(AggregateNameToNodes.TryGetValue(Name, out Nodes))
|
|
{
|
|
OutOutputs = Nodes.SelectMany(x => x.Outputs.Union(x.Inputs)).Distinct().ToArray();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Otherwise fail
|
|
OutOutputs = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cull the graph to only include the given nodes and their dependencies
|
|
/// </summary>
|
|
/// <param name="TargetNodes">A set of target nodes to build</param>
|
|
public void Select(IEnumerable<Node> TargetNodes)
|
|
{
|
|
// Find this node and all its dependencies
|
|
HashSet<Node> RetainNodes = new HashSet<Node>(TargetNodes);
|
|
foreach(Node TargetNode in TargetNodes)
|
|
{
|
|
RetainNodes.UnionWith(TargetNode.InputDependencies);
|
|
}
|
|
|
|
// Remove all the nodes which are not marked to be kept
|
|
foreach(AgentGroup Group in Groups)
|
|
{
|
|
Group.Nodes = Group.Nodes.Where(x => RetainNodes.Contains(x)).ToList();
|
|
}
|
|
|
|
// Remove all the empty groups
|
|
Groups.RemoveAll(x => x.Nodes.Count == 0);
|
|
|
|
// Remove all the order dependencies which are no longer part of the graph. Since we don't need to build them, we don't need to wait for them
|
|
foreach(Node Node in RetainNodes)
|
|
{
|
|
Node.OrderDependencies = Node.OrderDependencies.Where(x => RetainNodes.Contains(x)).ToArray();
|
|
}
|
|
|
|
// Create a new list of triggers from all the nodes which are left
|
|
NameToTrigger = RetainNodes.Where(x => x.ControllingTrigger != null).Select(x => x.ControllingTrigger).Distinct().ToDictionary(x => x.Name, x => x, StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
// Create a new list of aggregates for everything that's left
|
|
AggregateNameToNodes = AggregateNameToNodes.Where(x => x.Value.All(y => RetainNodes.Contains(y))).ToDictionary(Pair => Pair.Key, Pair => Pair.Value);
|
|
|
|
// Remove any diagnostics which are no longer part of the graph
|
|
Diagnostics.RemoveAll(x => (x.EnclosingNode != null && !RetainNodes.Contains(x.EnclosingNode)) || (x.EnclosingGroup != null && !Groups.Contains(x.EnclosingGroup)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Export the build graph to a Json file, for parallel execution by the build system
|
|
/// </summary>
|
|
/// <param name="File">Output file to write</param>
|
|
/// <param name="ActivatedTriggers">Set of triggers which have been activated</param>
|
|
/// <param name="CompletedNodes">Set of nodes which have been completed</param>
|
|
public void Export(FileReference File, HashSet<ManualTrigger> ActivatedTriggers, HashSet<Node> CompletedNodes)
|
|
{
|
|
// Find all the nodes which we're actually going to execute. We'll use this to filter the graph.
|
|
HashSet<Node> NodesToExecute = new HashSet<Node>();
|
|
foreach(Node Node in Groups.SelectMany(x => x.Nodes))
|
|
{
|
|
if(!CompletedNodes.Contains(Node))
|
|
{
|
|
if(Node.ControllingTrigger == null || ActivatedTriggers.Contains(Node.ControllingTrigger))
|
|
{
|
|
NodesToExecute.Add(Node);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Open the output file
|
|
using(JsonWriter JsonWriter = new JsonWriter(File.FullName))
|
|
{
|
|
JsonWriter.WriteObjectStart();
|
|
|
|
// Write all the agent groups
|
|
JsonWriter.WriteArrayStart("Groups");
|
|
foreach(AgentGroup Group in Groups)
|
|
{
|
|
Node[] Nodes = Group.Nodes.Where(x => NodesToExecute.Contains(x)).ToArray();
|
|
if(Nodes.Length > 0)
|
|
{
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", Group.Name);
|
|
JsonWriter.WriteArrayStart("Agent Types");
|
|
foreach(string AgentType in Group.PossibleTypes)
|
|
{
|
|
JsonWriter.WriteValue(AgentType);
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
JsonWriter.WriteArrayStart("Nodes");
|
|
foreach(Node Node in Nodes)
|
|
{
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", Node.Name);
|
|
JsonWriter.WriteValue("DependsOn", String.Join(";", Node.GetDirectOrderDependencies().Where(x => !CompletedNodes.Contains(x))));
|
|
JsonWriter.WriteObjectStart("Notify");
|
|
JsonWriter.WriteValue("Default", String.Join(";", Node.NotifyUsers));
|
|
JsonWriter.WriteValue("Submitters", String.Join(";", Node.NotifySubmitters));
|
|
JsonWriter.WriteValue("Warnings", Node.bNotifyOnWarnings);
|
|
JsonWriter.WriteObjectEnd();
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
|
|
// Write all the triggers
|
|
JsonWriter.WriteArrayStart("Triggers");
|
|
foreach (ManualTrigger Trigger in NameToTrigger.Values)
|
|
{
|
|
if(!ActivatedTriggers.Contains(Trigger) && NodesToExecute.Any(x => x.ControllingTrigger == Trigger.Parent))
|
|
{
|
|
// Find all the nodes that this trigger is dependent on
|
|
HashSet<Node> Dependencies = new HashSet<Node>();
|
|
foreach(Node Node in Groups.SelectMany(x => x.Nodes))
|
|
{
|
|
for(ManualTrigger ControllingTrigger = Node.ControllingTrigger; ControllingTrigger != null; ControllingTrigger = ControllingTrigger.Parent)
|
|
{
|
|
if(ControllingTrigger == Trigger)
|
|
{
|
|
Dependencies.UnionWith(Node.OrderDependencies.Where(x => x.ControllingTrigger != Trigger && NodesToExecute.Contains(x)));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reduce that list to the smallest subset of direct dependencies
|
|
HashSet<Node> DirectDependencies = new HashSet<Node>(Dependencies);
|
|
foreach(Node Dependency in Dependencies)
|
|
{
|
|
DirectDependencies.ExceptWith(Dependency.OrderDependencies);
|
|
}
|
|
|
|
// Write out the object
|
|
JsonWriter.WriteObjectStart();
|
|
JsonWriter.WriteValue("Name", Trigger.Name);
|
|
JsonWriter.WriteValue("AllDependencies", String.Join(";", Groups.SelectMany(x => x.Nodes).Where(x => Dependencies.Contains(x)).Select(x => x.Name)));
|
|
JsonWriter.WriteValue("DirectDependencies", String.Join(";", Dependencies.Where(x => DirectDependencies.Contains(x)).Select(x => x.Name)));
|
|
JsonWriter.WriteValue("Notify", String.Join(";", Trigger.NotifyUsers));
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
JsonWriter.WriteArrayEnd();
|
|
JsonWriter.WriteObjectEnd();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Print the contents of the graph
|
|
/// </summary>
|
|
/// <param name="NodeToState">Mapping of node to its current state</param>
|
|
/// <param name="Options">Options for how to print the graph</param>
|
|
public void Print(HashSet<Node> CompletedNodes, GraphPrintOptions Options)
|
|
{
|
|
// Get a list of all the triggers, including the null global one
|
|
List<ManualTrigger> AllTriggers = new List<ManualTrigger>();
|
|
AllTriggers.Add(null);
|
|
AllTriggers.AddRange(NameToTrigger.Values.OrderBy(x => x.QualifiedName));
|
|
|
|
// Output all the triggers in order
|
|
CommandUtils.Log("");
|
|
CommandUtils.Log("Graph:");
|
|
foreach(ManualTrigger Trigger in AllTriggers)
|
|
{
|
|
// Filter everything by this trigger
|
|
Dictionary<AgentGroup, Node[]> FilteredGroupToNodes = new Dictionary<AgentGroup,Node[]>();
|
|
foreach(AgentGroup Group in Groups)
|
|
{
|
|
Node[] Nodes = Group.Nodes.Where(x => x.ControllingTrigger == Trigger).ToArray();
|
|
if(Nodes.Length > 0)
|
|
{
|
|
FilteredGroupToNodes[Group] = Nodes;
|
|
}
|
|
}
|
|
|
|
// Skip this trigger if there's nothing to display
|
|
if(FilteredGroupToNodes.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Print the trigger name
|
|
CommandUtils.Log(" Trigger: {0}", (Trigger == null)? "None" : Trigger.QualifiedName);
|
|
if(Trigger != null && Options.HasFlag(GraphPrintOptions.ShowNotifications))
|
|
{
|
|
foreach(string User in Trigger.NotifyUsers)
|
|
{
|
|
CommandUtils.Log(" notify> {0}", User);
|
|
}
|
|
}
|
|
|
|
// Output all the groups for this trigger
|
|
foreach(AgentGroup Group in Groups)
|
|
{
|
|
Node[] Nodes;
|
|
if(FilteredGroupToNodes.TryGetValue(Group, out Nodes))
|
|
{
|
|
CommandUtils.Log(" Group: {0} ({1})", Group.Name, String.Join(";", Group.PossibleTypes));
|
|
foreach(Node Node in Nodes)
|
|
{
|
|
CommandUtils.Log(" Node: {0}{1}", Node.Name, CompletedNodes.Contains(Node)? " (completed)" : "");
|
|
if(Options.HasFlag(GraphPrintOptions.ShowDependencies))
|
|
{
|
|
HashSet<Node> InputDependencies = new HashSet<Node>(Node.GetDirectInputDependencies());
|
|
foreach(Node InputDependency in InputDependencies)
|
|
{
|
|
CommandUtils.Log(" input> {0}", InputDependency.Name);
|
|
}
|
|
HashSet<Node> OrderDependencies = new HashSet<Node>(Node.GetDirectOrderDependencies());
|
|
foreach(Node OrderDependency in OrderDependencies.Except(InputDependencies))
|
|
{
|
|
CommandUtils.Log(" after> {0}", OrderDependency.Name);
|
|
}
|
|
}
|
|
if(Options.HasFlag(GraphPrintOptions.ShowNotifications))
|
|
{
|
|
string Label = Node.bNotifyOnWarnings? "warnings" : "errors";
|
|
foreach(string User in Node.NotifyUsers)
|
|
{
|
|
CommandUtils.Log(" {0}> {1}", Label, User);
|
|
}
|
|
foreach(string Submitter in Node.NotifySubmitters)
|
|
{
|
|
CommandUtils.Log(" {0}> submitters to {1}", Label, Submitter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
CommandUtils.Log("");
|
|
|
|
// Print out all the aggregates
|
|
string[] AggregateNames = AggregateNameToNodes.Keys.OrderBy(x => x).ToArray();
|
|
if(AggregateNames.Length > 0)
|
|
{
|
|
CommandUtils.Log("Aggregates:");
|
|
foreach(string AggregateName in AggregateNames)
|
|
{
|
|
CommandUtils.Log(" {0}", AggregateName);
|
|
}
|
|
CommandUtils.Log("");
|
|
}
|
|
}
|
|
}
|
|
}
|