MomentReferenceBuilder.cs
1 using Logging; 2 using Newtonsoft.Json; 3 4 namespace OverwatchTranscript 5 { 6 public class MomentReferenceBuilder 7 { 8 private const int MaxMomentsPerReference = 10000; 9 private readonly ILog log; 10 private readonly string workingDir; 11 12 public MomentReferenceBuilder(ILog log, string workingDir) 13 { 14 this.log = log; 15 this.workingDir = workingDir; 16 } 17 18 public OverwatchMomentReference[] Build(IFinalizedBucket[] finalizedBuckets) 19 { 20 var result = new List<OverwatchMomentReference>(); 21 var currentBuilder = new Builder(log, workingDir); 22 23 var buckets = finalizedBuckets.ToList(); 24 log.Debug($"Building references for {buckets.Count} buckets."); 25 while (buckets.Any()) 26 { 27 foreach (var b in buckets) b.Update(); 28 29 buckets.RemoveAll(b => b.IsEmpty); 30 if (!buckets.Any()) break; 31 32 var earliestUtc = GetEarliestUtc(buckets); 33 if (earliestUtc == null) continue; 34 35 var tops = CollectAllTopsForUtc(earliestUtc.Value, buckets); 36 var moment = ConvertTopsToMoment(tops); 37 currentBuilder.Add(moment); 38 if (currentBuilder.NumberOfMoments == MaxMomentsPerReference) 39 { 40 result.Add(currentBuilder.Build()); 41 currentBuilder = new Builder(log, workingDir); 42 } 43 } 44 45 if (currentBuilder.NumberOfMoments > 0) 46 { 47 result.Add(currentBuilder.Build()); 48 } 49 50 return result.ToArray(); 51 } 52 53 private OverwatchMoment ConvertTopsToMoment(List<BucketTop> tops) 54 { 55 var discintUtc = tops.Select(e => e.Utc).Distinct().ToArray(); 56 if (discintUtc.Length != 1) throw new Exception("UTC mixing in moment construction."); 57 58 return new OverwatchMoment 59 { 60 Utc = tops[0].Utc, 61 Events = tops.SelectMany(e => e.Events).ToArray() 62 }; 63 } 64 65 private List<BucketTop> CollectAllTopsForUtc(DateTime earliestUtc, List<IFinalizedBucket> buckets) 66 { 67 var result = new List<BucketTop>(); 68 69 foreach (var bucket in buckets) 70 { 71 if (bucket.IsEmpty) continue; 72 73 var utc = bucket.SeeTopUtc(); 74 if (utc == null) continue; 75 76 if (utc.Value == earliestUtc) 77 { 78 var top = bucket.TakeTop(); 79 if (top == null) throw new Exception("top was null after top utc was not"); 80 result.Add(top); 81 } 82 } 83 84 return result; 85 } 86 87 private DateTime? GetEarliestUtc(List<IFinalizedBucket> buckets) 88 { 89 var earliest = DateTime.MaxValue; 90 foreach (var bucket in buckets) 91 { 92 var utc = bucket.SeeTopUtc(); 93 if (utc == null) return null; 94 95 if (utc.Value < earliest) earliest = utc.Value; 96 } 97 return earliest; 98 } 99 100 public class Builder 101 { 102 private readonly ILog log; 103 private readonly string workingDir; 104 private OverwatchMomentReference reference; 105 private readonly ActionQueue queue = new ActionQueue(); 106 107 public Builder(ILog log, string workingDir) 108 { 109 reference = new OverwatchMomentReference 110 { 111 MomentsFile = Guid.NewGuid().ToString(), 112 EarliestUtc = DateTime.MaxValue, 113 LatestUtc = DateTime.MinValue, 114 NumberOfEvents = 0, 115 NumberOfMoments = 0, 116 }; 117 this.log = log; 118 this.workingDir = workingDir; 119 queue.Start(); 120 } 121 122 public int NumberOfMoments => reference.NumberOfMoments; 123 124 public void Add(OverwatchMoment moment) 125 { 126 if (moment.Utc < reference.EarliestUtc) reference.EarliestUtc = moment.Utc; 127 if (moment.Utc > reference.LatestUtc) reference.LatestUtc = moment.Utc; 128 reference.NumberOfMoments++; 129 reference.NumberOfEvents += moment.Events.Length; 130 131 var filePath = Path.Combine(workingDir, reference.MomentsFile); 132 133 queue.Add(() => 134 { 135 File.AppendAllLines(filePath, new[] 136 { 137 Json.Serialize(moment) 138 }); 139 }); 140 } 141 142 public OverwatchMomentReference Build() 143 { 144 queue.StopAndJoin(); 145 146 log.Debug($"Created reference with {reference.NumberOfMoments} moments and {reference.NumberOfEvents} events..."); 147 var result = reference; 148 reference = null!; 149 return result; 150 } 151 } 152 } 153 }