Ejemplo n.º 1
0
		public BuildResult GenBuilds(string prefix = "") {
			if (Type == BuildType.Lock) {
				Best = new Monster(Mon, true);
				return BuildResult.Success;
			}
			else if (Type == BuildType.Link) {
				if (LinkBuild == null) {
					for (int i = 0; i < 6; i++)
						runes[i] = new Rune[0];
					return BuildResult.Failure;
				}
				else {
					CopyFrom(LinkBuild);
				}
			}

			if (runes.Any(r => r == null)) {
				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Null rune"));
				return BuildResult.BadRune;
			}
			if (!BuildSaveStats)
				BuildGoodRunes = false;

			if (!BuildGoodRunes) {
				RuneUsage = new RuneUsage();
				BuildUsage = new BuildUsage();
			}
			try {
				// if to get awakened
				if (DownloadAwake && !Mon.downloaded) {
					BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Downloading Awake def"));
					var mref = MonsterStat.FindMon(Mon);
					if (mref != null) {
						// download the current (unawakened monster)
						var mstat = mref.Download();
						BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Reading stats"));
						// if the retrieved mon is unawakened, get the awakened
						if (!mstat.Awakened && mstat.AwakenTo != null) {
							BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Awakening"));
							Mon = mstat.AwakenTo.Download().GetMon(Mon);
						}
					}
					BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Downloaded"));
				}
				// getting awakened also gets level 40, so...
				// only get lvl 40 stats if the monster isn't 40, wants to download AND isn't already downloaded (first and last are about the same)
				else if (Mon.level < 40 && DownloadStats && !Mon.downloaded) {
					BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Downloading 40 def"));
					var mref = MonsterStat.FindMon(Mon);
					if (mref != null)
						Mon = mref.Download().GetMon(Mon);
				}
			}
			catch (Exception e) {
				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Failed downloading def: " + e.Message + Environment.NewLine + e.StackTrace));
			}

			if (!getRunningHandle())
				throw new InvalidOperationException("The build is locked with another action.");
			
			Loads.Clear();

			if (!Sort[Attr.Speed].EqualTo(0) && Sort[Attr.Speed] <= 1 // 1 SPD is too good to pass
				|| Mon.Current.Runes.Any(r => r == null)
				|| !Mon.Current.Runes.All(r => runes[r.Slot - 1].Contains(r)) // only IgnoreLess5 if I have my own runes
				|| Sort.NonZeroStats.HasCount(1)) // if there is only 1 sorting, must be too important to drop???
				IgnoreLess5 = false;

			Thread timeThread = null;

			if (!string.IsNullOrWhiteSpace(BuildStrategy)) {

				var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(asm => asm.GetTypes().Where(t => typeof(IBuildStrategyDefinition).IsAssignableFrom(t)));

				var type = types.FirstOrDefault(t => t.AssemblyQualifiedName.Contains(BuildStrategy));
				if (type != null) {
					RunStrategy();
				}
			}

			tcs.TrySetResult(null);

			try {
				Best = null;
				Stats bstats = null;
				count = 0;
				actual = 0;
				total = runes[0].Length;
				total *= runes[1].Length;
				total *= runes[2].Length;
				total *= runes[3].Length;
				total *= runes[4].Length;
				total *= runes[5].Length;
				complete = total;

				Mon.ExtraCritRate = extraCritRate;
				Mon.GetStats();
				Mon.DamageFormula?.Invoke(Mon);

				int?[] slotFakesTemp = new int?[6];
				bool[] slotPred = new bool[6];
				getPrediction(slotFakesTemp, slotPred);

				int[] slotFakes = slotFakesTemp.Select(i => i ?? 0).ToArray();

				var currentLoad = new Monster(Mon, true);
				currentLoad.Current.TempLoad = true;
				currentLoad.Current.Buffs = Buffs;
				currentLoad.Current.Shrines = Shrines;
				currentLoad.Current.Leader = Leader;

				currentLoad.Current.FakeLevel = slotFakes;
				currentLoad.Current.PredictSubs = slotPred;

				double currentScore = CalcScore(currentLoad.GetStats(true));

				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "cooking"));

				if (total == 0) {
					BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "0 perms"));
					RuneLog.Info("Zero permuations");
					return BuildResult.NoPermutations;
				}

				bool hasSort = Sort.IsNonZero;
				if (BuildTake == 0 && !hasSort) {
					BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "No sort"));
					RuneLog.Info("No method of determining best");
					return BuildResult.NoSorting;
				}

				DateTime begin = DateTime.Now;

				RuneLog.Debug(count + "/" + total + "  " + string.Format("{0:P2}", (count + complete - total) / (double)complete));


				// set to running
				IsRunning = true;

#if BUILD_PRECHECK_BUILDS_DEBUG
				SynchronizedCollection<string> outstrs = new SynchronizedCollection<string>();
#endif
				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "..."));

				List<Monster> tests = new List<Monster>();

				timeThread = new Thread(() => {
					while (IsRunning) {
						if (RunesOnlyFillEmpty)
							Thread.Sleep(30 / ((Mon?.Current?.RuneCount ?? 1) + 1));
						else
							Thread.Sleep(400);
						// every second, give a bit of feedback to those watching
						RuneLog.Debug(count + "/" + total + "  " + string.Format("{0:P2}", (count + complete - total) / (double)complete));
						if (tests != null)
							BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, (count + complete - total) / (double)complete, tests.Count));

						if (BuildTimeout > 0 && DateTime.Now > begin.AddSeconds(BuildTimeout)) {
							RuneLog.Info("Timeout");
							BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "Timeout"));
							BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, 1, tests.Count));

							IsRunning = false;
						}
					}
				});
				timeThread.Start();

				double bestScore = double.MinValue;

				var opts = new ParallelOptions() {
					MaxDegreeOfParallelism = Environment.ProcessorCount - 1
				};

				var mmm = Maximum.NonZeroCached;

				// Parallel the outer loop
				// TODO: setup the begin/finish Actions with syncList.
				void body (Rune r0, ParallelLoopState loopState) {
					var tempReq = RequiredSets.ToList();
					var tempMax = Maximum == null || !Maximum.IsNonZero ? null : new Stats(Maximum, true);
					int tempCheck = 0;

					Monster myBest = null;
					List<Monster> syncList = new List<Monster>();

					void syncMyList() {
						lock (bestLock) {
#if DEBUG_SYNC_BUILDS
							foreach (var s in syncList) {
								if (s.Current.Runes.All(r => r.Assigned == mon)) {
									Console.WriteLine("!");
								}
							}
#endif
							tests.AddRange(syncList);

						}
						//syncList.ForEach(_ => tests.Add(_));
						syncList.Clear();
						if (tests.Count > Math.Max(BuildGenerate, 250000)) {
#if DEBUG_SYNC_BUILDS
								var rems = tests.OrderByDescending(b => b.score).Skip(75000).ToList();
								foreach (var bbb in rems) {
									if (bbb.Current.Runes.All(r => r.Assigned == mon)) {
										Console.WriteLine("!");
									}
								}
#endif
							lock (bestLock) {
								tests = tests.OrderByDescending(b => b.score).Take(75000).ToList();
							}
						}

						if (tests.Count > MaxBuilds32)
							IsRunning = false;
					}

					if (!IsRunning_Unsafe) {
						syncMyList();
						loopState.Break();
					}

					// number of builds ruled out since last sync
					int kill = 0;
					// number of builds added since last sync
					int plus = 0;
					// number of builds skipped
					int skip = 0;


					bool isBad;
					double myBestScore = double.MinValue, curScore, lastBest = double.MinValue;
					Stats cstats, myStats;


					Monster test = new Monster(Mon);
					test.Current.TempLoad = true;
					test.Current.Buffs = Buffs;
					test.Current.Shrines = Shrines;
					test.Current.Leader = Leader;

					test.Current.FakeLevel = slotFakes;
					test.Current.PredictSubs = slotPred;
					test.ApplyRune(r0, 7);

					RuneSet set4 = r0.SetIs4 ? r0.Set : RuneSet.Null;
					RuneSet set2 = r0.SetIs4 ? RuneSet.Null : r0.Set;
					int pop4 = 0;
					int pop2 = 0;

					foreach (Rune r1 in runes[1]) {
						// TODO: refactor to local method
						if (!IsRunning_Unsafe) // Can't break to a label, don't want to goto
							break;
						// TODO: break into multiple implementations that have less branching
#if BUILD_PRECHECK_BUILDS
						if (!AllowBroken && !RunesOnlyFillEmpty) {
							if (r1.SetIs4) {
								if (pop2 == 2)
									pop2 = 7;
								if (set4 == RuneSet.Null || pop4 >= 2) {
									set4 = r1.Set;
									pop4 = 2;
								}
								else if (set4 != r1.Set) {
#if BUILD_PRECHECK_BUILDS_DEBUG
									outstrs.Add($"bad4@2 {set4} {set2} | {r0.Set} {r1.Set}");
#endif
									skip += runes[2].Length * runes[3].Length * runes[4].Length * runes[5].Length;
									continue;
								}
							}
							else {
								if (pop4 == 2)
									pop4 = 7;
								if (set2 == RuneSet.Null || pop2 >= 2) {
									set2 = r1.Set;
									pop2 = 2;
								}
							}
						}
#endif
						test.ApplyRune(r1, 7);

						foreach (Rune r2 in runes[2]) {
							if (!IsRunning_Unsafe)
								break;
#if BUILD_PRECHECK_BUILDS
							if (!AllowBroken && !RunesOnlyFillEmpty) {
								if (r2.SetIs4) {
									if (pop2 == 3)
										pop2 = 7;
									if (set4 == RuneSet.Null || pop4 >= 3) {
										set4 = r2.Set;
										pop4 = 3;
									}
									else if (set4 != r2.Set) {
#if BUILD_PRECHECK_BUILDS_DEBUG
										outstrs.Add($"bad4@3 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set}");
#endif
										skip += runes[3].Length * runes[4].Length * runes[5].Length;
										continue;
									}
								}
								else {
									if (pop4 == 3)
										pop4 = 7;
									if (set2 == RuneSet.Null || pop2 >= 3) {
										set2 = r2.Set;
										pop2 = 3;
									}
									else if (set4 != RuneSet.Null && set2 != r2.Set) {
#if BUILD_PRECHECK_BUILDS_DEBUG
										outstrs.Add($"bad2@3 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set}");
#endif
										skip += runes[3].Length * runes[4].Length * runes[5].Length;
										continue;
									}
								}
							}
#endif
							test.ApplyRune(r2, 7);

							foreach (Rune r3 in runes[3]) {
								if (!IsRunning_Unsafe)
									break;
#if BUILD_PRECHECK_BUILDS
								if (!AllowBroken && !RunesOnlyFillEmpty) {
									if (r3.SetIs4) {
										if (pop2 == 4)
											pop2 = 7;
										if (set4 == RuneSet.Null || pop4 >= 4) {
											set4 = r3.Set;
											pop4 = 4;
										}
										else if (set4 != r3.Set) {
#if BUILD_PRECHECK_BUILDS_DEBUG
											outstrs.Add($"bad4@4 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set}");
#endif
											skip += runes[4].Length * runes[5].Length;
											continue;
										}
									}
									else {
										if (pop4 == 4)
											pop4 = 7;
										if (set2 == RuneSet.Null || pop2 >= 4) {
											set2 = r3.Set;
											pop2 = 4;
										}
										else if (set4 != RuneSet.Null && set2 != r3.Set) {
#if BUILD_PRECHECK_BUILDS_DEBUG
											outstrs.Add($"bad2@4 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set}");
#endif
											skip += runes[4].Length * runes[5].Length;
											continue;
										}
									}
								}
#endif
								test.ApplyRune(r3, 7);

								foreach (Rune r4 in runes[4]) {
									if (!IsRunning_Unsafe) {
										break;
									}
#if BUILD_PRECHECK_BUILDS
									if (!AllowBroken && !RunesOnlyFillEmpty) {
										if (r4.SetIs4) {
											if (pop2 == 5)
												pop2 = 7;
											if (set4 == RuneSet.Null || pop4 >= 5) {
												set4 = r4.Set;
												pop4 = 5;
											}
											else if (set4 != r4.Set) {
#if BUILD_PRECHECK_BUILDS_DEBUG
												outstrs.Add($"bad4@5 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set} {r4.Set}");
#endif
												skip += runes[5].Length;
												continue;
											}
										}
										else {
											if (pop4 == 5)
												pop4 = 7;
											if (set2 == RuneSet.Null || pop2 >= 5) {
												set2 = r4.Set;
												pop2 = 5;

											}
											else if (set4 != RuneSet.Null && set2 != r4.Set) {
#if BUILD_PRECHECK_BUILDS_DEBUG
												outstrs.Add($"bad2@5 {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set} {r4.Set}");
#endif
												skip += runes[5].Length;
												continue;
											}
										}
									}
#endif
									test.ApplyRune(r4, 7);

									foreach (Rune r5 in runes[5]) {
										if (!IsRunning_Unsafe)
											break;

										test.ApplyRune(r5, 7);
										test.Current.CheckSets();
#if BUILD_PRECHECK_BUILDS_DEBUG
										outstrs.Add($"fine {set4} {set2} | {r0.Set} {r1.Set} {r2.Set} {r3.Set} {r4.Set} {r5.Set}");
#endif
										isBad = false;

										cstats = test.GetStats();

										// check if build meets minimum
										isBad |= !RunesOnlyFillEmpty && !AllowBroken && !test.Current.SetsFull;

										isBad |= tempMax != null && cstats.AnyExceedCached(tempMax);

										if (!isBad && GrindLoads) {
											var mahGrinds = grinds.ToList();
											for (int rg = 0; rg < 6; rg++) {
												var lgrinds = test.Runes[rg].FilterGrinds(mahGrinds);
												foreach (var g in lgrinds) {
													var tr = test.Runes[rg].Grind(g);
												}
												// TODO: 
											}
										}

										isBad |= !RunesOnlyFillEmpty && Minimum != null && !cstats.GreaterEqual(Minimum, true);
										// if no broken sets, check for broken sets
										// if there are required sets, ensure we have them
										/*isBad |= (tempReq != null && tempReq.Count > 0
											// this Linq adds no overhead compared to GetStats() and ApplyRune()
											//&& !tempReq.All(s => test.Current.Sets.Count(q => q == s) >= tempReq.Count(q => q == s))
											//&& !tempReq.GroupBy(s => s).All(s => test.Current.Sets.Count(q => q == s.Key) >= s.Count())
											);*/
										// TODO: recheck this code is correct
										if (tempReq != null && tempReq.Count > 0) {
											tempCheck = 0;
											foreach (var r in tempReq) {
												int i;
												for (i = 0; i < 3; i++) {
													if (test.Current.Sets[i] == r && (tempCheck & 1 << i) != 1 << i) {
														tempCheck |= 1 << i;
														break;
													}
												}
												if (i >= 3) {
													isBad |= true;
													break;
												}
											}
										}

										if (isBad) {
											kill++;
											curScore = 0;
										}
										else {
											// try to reduce CalcScore hits
											curScore = CalcScore(cstats);
											isBad |= IgnoreLess5 && curScore < currentScore * 1.05;
											if (isBad)
												kill++;
										}

										if (!isBad) {
											// we found an okay build!
											plus++;
											test.score = curScore;

											if (BuildSaveStats) {
												foreach (Rune r in test.Current.Runes) {
													if (!BuildGoodRunes) {
														r.manageStats.AddOrUpdate("LoadFilt", 1, (s, d) => { return d + 1; });
														RuneUsage.runesGood.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot);
														r.manageStats.AddOrUpdate("currentBuildPoints", curScore, (k, v) => Math.Max(v, curScore));
														r.manageStats.AddOrUpdate("cbp" + ID, curScore, (k, v) => Math.Max(v, curScore));
													}
													else {
														r.manageStats.AddOrUpdate("LoadFilt", 0.001, (s, d) => { return d + 0.001; });
														RuneUsage.runesOkay.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot);
														r.manageStats.AddOrUpdate("cbp" + ID, curScore, (k, v) => Math.Max(v, curScore * 0.9));
													}
												}
											}

											if (syncList.Count >= 500) {
												syncMyList();
											}

											// if we are to track all good builds, keep it
											if (!BuildDumpBads) {

												syncList.Add(new Monster(test, true));

												// locally track my best
												if (myBest == null || curScore > myBestScore) {
													myBest = new Monster(test, true);
													myStats = myBest.GetStats();
													myBestScore = CalcScore(myStats);
													myBest.score = myBestScore;
												}

												// if mine is better than what I last saw
												if (myBestScore > lastBest) {
													lock (bestLock) {
														if (Best == null) {
															Best = new Monster(myBest, true);
															bstats = Best.GetStats();
															bestScore = CalcScore(bstats);
															Best.score = bestScore;
														}
														else {
															// sync best score
															lastBest = bestScore;
															// double check
															if (myBestScore > lastBest) {
																Best = new Monster(myBest, true);
																bestScore = CalcScore(bstats);
																Best.score = bestScore;
																bstats = Best.GetStats();
															}
														}
													}
												}

											}
											// if we only want to track really good builds
											else {
												// if there are currently no good builds, keep it
												// or if this build is better than the best, keep it

												// locally track my best
												if (myBest == null || curScore > myBestScore) {
													myBest = new Monster(test, true);
													myStats = myBest.GetStats();
													myBestScore = CalcScore(myStats);
													myBest.score = myBestScore;
													syncList.Add(myBest);
												}
												else if (BuildSaveStats) {
													// keep it for spreadsheeting
													syncList.Add(new Monster(test, true) {
														score = curScore
													});
												}
											}
										}
									}
									// sum up what work we've done
									Interlocked.Add(ref count, kill);
									Interlocked.Add(ref count, skip);
									Interlocked.Add(ref skipped, skip);
									Interlocked.Add(ref actual, kill);
									Interlocked.Add(ref BuildUsage.failed, kill);
									kill = 0;
									skip = 0;
									Interlocked.Add(ref count, plus);
									Interlocked.Add(ref actual, plus);
									Interlocked.Add(ref BuildUsage.passed, plus);
									plus = 0;

									// if we've got enough, stop
									if (BuildGenerate > 0 && BuildUsage.passed >= BuildGenerate) {
										IsRunning = false;
										break;
									}
								}
							}
						}
					}
					// just before dying
					syncMyList();
				}
				Parallel.ForEach(runes[0], opts, body);


				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "finalizing..."));
				BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, 0.99, -1));

#if BUILD_PRECHECK_BUILDS_DEBUG
				System.IO.File.WriteAllLines("_into_the_bridge.txt", outstrs.ToArray());
#endif
				if (BuildSaveStats) {
					foreach (var ra in runes) {
						foreach (var r in ra) {
							if (!BuildGoodRunes) {
								r.manageStats.AddOrUpdate("buildScoreTotal", CalcScore(Best), (k, v) => v + CalcScore(Best));
								RuneUsage.runesUsed.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot);
								r.manageStats.AddOrUpdate("LoadGen", total, (s, d) => { return d + total; });

							}
							else {
								RuneUsage.runesBetter.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot);
								r.manageStats.AddOrUpdate("LoadGen", total * 0.001, (s, d) => { return d + total * 0.001; });
							}
						}
					}
				}

				// write out completion
				RuneLog.Debug(IsRunning + " " + count + "/" + total + "  " + string.Format("{0:P2}", (count + complete - total) / (double)complete));
				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + " completed"));
				BuildProgTo?.Invoke(this, ProgToEventArgs.GetEvent(this, 1, tests.Count));

				// sort *all* the builds
				int takeAmount = 1;
				if (BuildSaveStats)
					takeAmount = 10;
				if (BuildTake > 0)
					takeAmount = BuildTake;

				if (IgnoreLess5)
					tests.Add(new Monster(Mon, true));

				foreach (var ll in tests.Where(t => t != null).OrderByDescending(r => CalcScore(r.GetStats())).Take(takeAmount))
					Loads.Add(ll);

				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, "Found a load " + Loads.Count()));

				if (!BuildGoodRunes)
					BuildUsage.loads = tests.ToList();

				// dump everything to console, if nothing to print to
				if (BuildPrintTo == null)
					foreach (var l in Loads) {
						RuneLog.Debug(l.GetStats().Health + "  " + l.GetStats().Attack + "  " + l.GetStats().Defense + "  " + l.GetStats().Speed
							+ "  " + l.GetStats().CritRate + "%" + "  " + l.GetStats().CritDamage + "%" + "  " + l.GetStats().Resistance + "%" + "  " + l.GetStats().Accuracy + "%");
					}

				// sadface if no builds
				if (!Loads.Any()) {
					RuneLog.Info("No builds :(");
					BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "Zero :("));
				}
				else {
					// remember the good one
					Best = Loads.First();
					Best.Current.TempLoad = false;
					Best.score = CalcScore(Best.GetStats());
					BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "best " + (Best?.score ?? -1)));
					Best.Current.ActualTests = actual;
					foreach (var bb in Loads) {
						foreach (Rune r in bb.Current.Runes) {
							double val = Best.score;
							if (BuildGoodRunes) {
								val *= 0.25;
								if (bb == Best)
									RuneUsage.runesSecond.AddOrUpdate(r, (byte)r.Slot, (key, ov) => (byte)r.Slot);
							}

							if (bb != Best)
								val *= 0.1;
							else
								r.manageStats.AddOrUpdate("In", BuildGoodRunes ? 2 : 1, (s, e) => BuildGoodRunes ? 2 : 1);

							r.manageStats.AddOrUpdate("buildScoreIn", val, (k, v) => v + val);
						}
					}
					for (int i = 0; i < 6; i++) {
						if (!BuildGoodRunes && Mon.Current.Runes[i] != null && Mon.Current.Runes[i].Id != Best.Current.Runes[i].Id)
							Mon.Current.Runes[i].Swapped = true;
					}
					foreach (var ra in runes) {
						foreach (var r in ra) {
							var cbp = r.manageStats.GetOrAdd("currentBuildPoints", 0);
							if (cbp / Best.score < 1)
								r.manageStats.AddOrUpdate("bestBuildPercent", cbp / Best.score, (k, v) => Math.Max(v, cbp / Best.score));
						}
					}
				}

				tests.Clear();
				tests = null;
				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + "Test cleared"));
				return BuildResult.Success;
			}
			catch (Exception e) {
				RuneLog.Error("Error " + e);
				BuildPrintTo?.Invoke(this, PrintToEventArgs.GetEvent(this, prefix + e.ToString()));
				return BuildResult.Failure;
			}
			finally {
				tcs = new TaskCompletionSource<IBuildRunner>();
				IsRunning = false;
				if (timeThread != null)
					timeThread.Join();
			}
		}