Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
EnumerableBuildStep.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Threading.Tasks;
7 using SiliconStudio.Core.Storage;
8 using SiliconStudio.Core.Serialization.Assets;
9 
10 namespace SiliconStudio.BuildEngine
11 {
12  /// <summary>
13  /// A <see cref="BuildStep"/> that can spawn multiple <see cref="BuildStep"/>.
14  /// Input and output tracking and merging will be performed.
15  /// Various input/output and output/output conflicts are detected, if <see cref="WaitBuildStep"/> is not used properly.
16  /// </summary>
17  public abstract class EnumerableBuildStep : BuildStep
18  {
19  /// <inheritdoc />
20  public override string Title { get { return ToString(); } }
21 
22  private int mergeCounter;
23  private readonly List<BuildStep> executedSteps = new List<BuildStep>();
24 
25  protected readonly Dictionary<ObjectUrl, OutputObject> outputObjects = new Dictionary<ObjectUrl, OutputObject>();
26  public IDictionary<ObjectUrl, OutputObject> OutputObjects { get { return outputObjects; } }
27 
28  protected readonly Dictionary<ObjectUrl, InputObject> inputObjects = new Dictionary<ObjectUrl, InputObject>();
30 
31  public IEnumerable<BuildStep> Steps { get; set; }
32 
33  protected EnumerableBuildStep()
34  {
35  InputUrls = inputObjects.Keys;
36  }
37 
38  protected EnumerableBuildStep(IEnumerable<BuildStep> steps) : this()
39  {
40  Steps = steps;
41  }
42 
43  public override async Task<ResultStatus> Execute(IExecuteContext executeContext, BuilderContext builderContext)
44  {
45  var buildStepsToWait = new List<BuildStep>();
46 
47  foreach (var child in Steps)
48  {
49  // Wait for all the tasks before the WaitBuildStep to be finished
50  if (child is WaitBuildStep)
51  {
52  await CompleteCommands(executeContext, buildStepsToWait);
53  }
54  else
55  {
56  executeContext.ScheduleBuildStep(child);
57  buildStepsToWait.Add(child);
58  }
59 
60  executedSteps.Add(child);
61  }
62 
63  await CompleteCommands(executeContext, buildStepsToWait);
64 
65  return ComputeResultStatusFromExecutedSteps();
66  }
67 
68  /// <summary>
69  /// Determine the result status of an execution of enumeration of build steps.
70  /// </summary>
71  /// <returns>The result status of the execution.</returns>
73  {
74  if (executedSteps.Count == 0)
75  return ResultStatus.Successful;
76 
77  // determine the result status of the list based on the children executed steps
78  // -> One or more children canceled => canceled
79  // -> One or more children failed (Prerequisite or Command) and none canceled => failed
80  // -> One or more children succeeded and none canceled nor failed => succeeded
81  // -> All the children were successful without triggering => not triggered was successful
82  var result = executedSteps[0].Status;
83  foreach (var executedStep in executedSteps)
84  {
85  if (executedStep.Status == ResultStatus.Cancelled)
86  {
87  result = ResultStatus.Cancelled;
88  break;
89  }
90 
91  if (executedStep.Failed)
92  result = ResultStatus.Failed;
93  else if (executedStep.Status == ResultStatus.Successful && result != ResultStatus.Failed)
94  result = ResultStatus.Successful;
95  }
96 
97  return result;
98  }
99 
100  /// <summary>
101  /// Wait for given build steps to finish, then processes their inputs and outputs.
102  /// </summary>
103  /// <param name="executeContext">The execute context.</param>
104  /// <param name="buildStepsToWait">The build steps to wait.</param>
105  /// <returns></returns>
106  protected async Task CompleteCommands(IExecuteContext executeContext, List<BuildStep> buildStepsToWait)
107  {
108  // Wait for steps to be finished
109  if (buildStepsToWait.Count > 0)
110  await Task.WhenAll(buildStepsToWait.Select(x => x.ExecutedAsync()));
111 
112  // Wait for spawned steps to be finished
113  await Task.WhenAll(buildStepsToWait.SelectMany(EnumerateSpawnedBuildSteps).Select(x => x.ExecutedAsync()));
114 
115  // TODO: Merge results of sub lists
116  foreach (var buildStep in buildStepsToWait)
117  {
118  var enumerableBuildStep = buildStep as EnumerableBuildStep;
119  if (enumerableBuildStep != null)
120  {
121  // Merge results from sub list
122 
123  // Step1: Check inputs/outputs conflicts
124  foreach (var inputObject in enumerableBuildStep.inputObjects)
125  {
126  CheckInputObject(executeContext, inputObject.Key, inputObject.Value.Command);
127  }
128 
129  foreach (var outputObject in enumerableBuildStep.OutputObjects)
130  {
131  CheckOutputObject(executeContext, outputObject.Key, outputObject.Value.ObjectId, outputObject.Value.Command);
132  }
133 
134  // Step2: Add inputs/outputs
135  foreach (var inputObject in enumerableBuildStep.inputObjects)
136  {
137  AddInputObject(executeContext, inputObject.Key, inputObject.Value.Command);
138  }
139 
140  foreach (var outputObject in enumerableBuildStep.OutputObjects)
141  {
142  var newOutputObject = AddOutputObject(executeContext, outputObject.Key, outputObject.Value.ObjectId, outputObject.Value.Command);
143 
144  // Merge tags
145  foreach (var tag in outputObject.Value.Tags)
146  {
147  newOutputObject.Tags.Add(tag);
148  }
149  }
150  }
151 
152  var commandBuildStep = buildStep as CommandBuildStep;
153  if (commandBuildStep != null)
154  {
155  // Merge results from spawned step
156  ProcessCommandBuildStepResult(executeContext, commandBuildStep);
157  }
158  }
159 
160  buildStepsToWait.Clear();
161  mergeCounter++;
162  }
163 
164  /// <summary>
165  /// Processes the results from a <see cref="CommandBuildStep"/>.
166  /// </summary>
167  /// <param name="executeContext">The execute context.</param>
168  /// <param name="buildStep">The build step.</param>
169  private void ProcessCommandBuildStepResult(IExecuteContext executeContext, CommandBuildStep buildStep)
170  {
171  foreach (var resultInputObject in buildStep.Command.GetInputFiles())
172  {
173  AddInputObject(executeContext, resultInputObject, buildStep.Command);
174  }
175 
176  if (buildStep.Result != null)
177  {
178  // Step1: Check inputs/outputs conflicts
179  foreach (var resultInputObject in buildStep.Result.InputDependencyVersions)
180  {
181  CheckInputObject(executeContext, resultInputObject.Key, buildStep.Command);
182  }
183 
184  foreach (var resultOutputObject in buildStep.Result.OutputObjects)
185  {
186  CheckOutputObject(executeContext, resultOutputObject.Key, resultOutputObject.Value, buildStep.Command);
187  }
188 
189  // Step2: Add inputs/outputs
190  foreach (var resultInputObject in buildStep.Result.InputDependencyVersions)
191  {
192  AddInputObject(executeContext, resultInputObject.Key, buildStep.Command);
193  }
194 
195  foreach (var resultOutputObject in buildStep.Result.OutputObjects)
196  {
197  AddOutputObject(executeContext, resultOutputObject.Key, resultOutputObject.Value, buildStep.Command);
198  }
199  }
200 
201  // Process recursively
202  // TODO: Wait for completion of spawned step in case Task didn't wait for them
203  foreach (var spawnedStep in buildStep.SpawnedSteps)
204  {
205  ProcessCommandBuildStepResult(executeContext, spawnedStep);
206  }
207 
208  if (buildStep.Result != null)
209  {
210  // Resolve tags from TagSymbol
211  // TODO: Handle removed tags
212  foreach (var tagGroup in buildStep.Result
213  .TagSymbols
214  .Where(x => buildStep.Command.TagSymbols.ContainsKey(x.Value))
215  .GroupBy(x => x.Key, x => buildStep.Command.TagSymbols[x.Value].RealName))
216  {
217  var url = tagGroup.Key;
218 
219  // TODO: Improve search complexity?
220  OutputObject outputObject;
221  if (outputObjects.TryGetValue(url, out outputObject))
222  {
223  outputObject.Tags.UnionWith(tagGroup);
224  }
225  }
226  }
227  }
228 
229  /// <summary>
230  /// Adds the input object. Will try to detect input/output conflicts.
231  /// </summary>
232  /// <param name="executeContext">The execute context.</param>
233  /// <param name="inputObjectUrl">The input object URL.</param>
234  /// <param name="command">The command.</param>
235  /// <exception cref="System.InvalidOperationException"></exception>
236  private void CheckInputObject(IExecuteContext executeContext, ObjectUrl inputObjectUrl, Command command)
237  {
238  OutputObject outputObject;
239  if (outputObjects.TryGetValue(inputObjectUrl, out outputObject)
240  && outputObject.Command != command
241  && outputObject.Counter == mergeCounter)
242  {
243  var error = string.Format("Command {0} is writing {1} while command {2} is reading it", outputObject.Command, inputObjectUrl, command);
244  executeContext.Logger.Error(error);
245  throw new InvalidOperationException(error);
246  }
247  }
248 
249  private void AddInputObject(IExecuteContext executeContext, ObjectUrl inputObjectUrl, Command command)
250  {
251  OutputObject outputObject;
252  if (outputObjects.TryGetValue(inputObjectUrl, out outputObject)
253  && mergeCounter > outputObject.Counter)
254  {
255  // Object was outputed by ourself, so reading it as input should be ignored.
256  return;
257  }
258 
259  inputObjects[inputObjectUrl] = new InputObject { Command = command, Counter = mergeCounter };
260  }
261 
262  /// <summary>
263  /// Adds the output object. Will try to detect input/output conflicts, and output with different <see cref="ObjectId" /> conflicts.
264  /// </summary>
265  /// <param name="executeContext">The execute context.</param>
266  /// <param name="outputObjectUrl">The output object URL.</param>
267  /// <param name="outputObjectId">The output object id.</param>
268  /// <param name="command">The command that produced the output object.</param>
269  /// <exception cref="System.InvalidOperationException">Two CommandBuildStep with same inputs did output different results.</exception>
270  private void CheckOutputObject(IExecuteContext executeContext, ObjectUrl outputObjectUrl, ObjectId outputObjectId, Command command)
271  {
272  InputObject inputObject;
273  if (inputObjects.TryGetValue(outputObjectUrl, out inputObject)
274  && inputObject.Command != command
275  && inputObject.Counter == mergeCounter)
276  {
277  var error = string.Format("Command {0} is writing {1} while command {2} is reading it", command, outputObjectUrl, inputObject.Command);
278  executeContext.Logger.Error(error);
279  throw new InvalidOperationException(error);
280  }
281  }
282 
283  private OutputObject AddOutputObject(IExecuteContext executeContext, ObjectUrl outputObjectUrl, ObjectId outputObjectId, Command command)
284  {
285  OutputObject outputObject;
286 
287  if (!outputObjects.TryGetValue(outputObjectUrl, out outputObject))
288  {
289  // New item?
290  outputObject = new OutputObject(outputObjectUrl, outputObjectId);
291  outputObjects.Add(outputObjectUrl, outputObject);
292  }
293  else
294  {
295  // ObjectId should be similar (if no Wait happened), otherwise two tasks spawned with same parameters did output different results
296  if (outputObject.ObjectId != outputObjectId && outputObject.Counter == mergeCounter)
297  {
298  var error = string.Format("Commands {0} and {1} are both writing {2} at the same time", command, outputObject.Command, outputObjectUrl);
299  executeContext.Logger.Error(error);
300  throw new InvalidOperationException(error);
301  }
302 
303  // Update new ObjectId
304  outputObject.ObjectId = outputObjectId;
305  }
306 
307  // Update Counter so that we know if a wait happened since this output object has been merged.
308  outputObject.Counter = mergeCounter;
309  outputObject.Command = command;
310 
311  return outputObject;
312  }
313 
314  private static IEnumerable<BuildStep> EnumerateSpawnedBuildSteps(BuildStep buildStep)
315  {
316  foreach (var spawnedStep in buildStep.SpawnedSteps)
317  {
318  yield return spawnedStep;
319  foreach (var childSpawnedStep in EnumerateSpawnedBuildSteps(spawnedStep))
320  {
321  yield return childSpawnedStep;
322  }
323  }
324  }
325 
326  protected struct InputObject
327  {
328  public Command Command;
329  public int Counter;
330  }
331  }
332 }
IEnumerable< CommandBuildStep > SpawnedSteps
List of commands that needs this command to be successfully executed before being processed ...
Definition: BuildStep.cs:77
A BuildStep that can spawn multiple BuildStep. Input and output tracking and merging will be performe...
readonly IEnumerable< ObjectUrl > InputUrls
CommandResultEntry Result
Command Result, set only after step completion. Not thread safe, should not be modified ...
Dictionary< ObjectUrl, ObjectId > InputDependencyVersions
Keys
Enumeration for keys.
Definition: Keys.cs:8
ResultStatus
Status of a command.
Definition: ResultStatus.cs:8
When embedded in a EnumerableBuildStep, this build step will force all previous computations to be fi...
override async Task< ResultStatus > Execute(IExecuteContext executeContext, BuilderContext builderContext)
Execute the BuildStep, usually resulting in scheduling tasks in the scheduler
async Task CompleteCommands(IExecuteContext executeContext, List< BuildStep > buildStepsToWait)
Wait for given build steps to finish, then processes their inputs and outputs.
virtual IEnumerable< ObjectUrl > GetInputFiles()
Gets the list of input files (that can be deduced without running the command, only from command para...
Definition: Command.cs:108
ResultStatus ComputeResultStatusFromExecutedSteps()
Determine the result status of an execution of enumeration of build steps.
A hash to uniquely identify data.
Definition: ObjectId.cs:13
EnumerableBuildStep(IEnumerable< BuildStep > steps)
List< KeyValuePair< ObjectUrl, string > > TagSymbols
Tags added for a given URL.
Dictionary< ObjectUrl, ObjectId > OutputObjects
Output object ids as saved in the object database.