using Logging; using Newtonsoft.Json; namespace OverwatchTranscript { public class MomentReferenceBuilder { private const int MaxMomentsPerReference = 10000; private readonly ILog log; private readonly string workingDir; public MomentReferenceBuilder(ILog log, string workingDir) { this.log = log; this.workingDir = workingDir; } public OverwatchMomentReference[] Build(IFinalizedBucket[] finalizedBuckets) { var result = new List(); var currentBuilder = new Builder(log, workingDir); var buckets = finalizedBuckets.ToList(); log.Debug($"Building references for {buckets.Count} buckets."); while (buckets.Any()) { buckets.RemoveAll(b => b.IsEmpty); if (!buckets.Any()) break; var earliestUtc = GetEarliestUtc(buckets); if (earliestUtc == null) continue; var tops = CollectAllTopsForUtc(earliestUtc.Value, buckets); var moment = ConvertTopsToMoment(tops); currentBuilder.Add(moment); if (currentBuilder.NumberOfMoments == MaxMomentsPerReference) { result.Add(currentBuilder.Build()); currentBuilder = new Builder(log, workingDir); } } if (currentBuilder.NumberOfMoments > 0) { result.Add(currentBuilder.Build()); } return result.ToArray(); } private OverwatchMoment ConvertTopsToMoment(List tops) { var discintUtc = tops.Select(e => e.Utc).Distinct().ToArray(); if (discintUtc.Length != 1) throw new Exception("UTC mixing in moment construction."); return new OverwatchMoment { Utc = tops[0].Utc, Events = tops.SelectMany(e => e.Events).ToArray() }; } private List CollectAllTopsForUtc(DateTime earliestUtc, List buckets) { var result = new List(); foreach (var bucket in buckets) { if (bucket.IsEmpty) continue; var utc = bucket.SeeTopUtc(); if (utc == null) continue; if (utc.Value == earliestUtc) { var top = bucket.TakeTop(); if (top == null) throw new Exception("top was null after top utc was not"); result.Add(top); } } return result; } private DateTime? GetEarliestUtc(List buckets) { var earliest = DateTime.MaxValue; foreach (var bucket in buckets) { var utc = bucket.SeeTopUtc(); if (utc == null) return null; if (utc.Value < earliest) earliest = utc.Value; } return earliest; } public class Builder { private readonly ILog log; private readonly string workingDir; private OverwatchMomentReference reference; private readonly ActionQueue queue = new ActionQueue(); public Builder(ILog log, string workingDir) { reference = new OverwatchMomentReference { MomentsFile = Guid.NewGuid().ToString(), EarliestUtc = DateTime.MaxValue, LatestUtc = DateTime.MinValue, NumberOfEvents = 0, NumberOfMoments = 0, }; this.log = log; this.workingDir = workingDir; queue.Start(); } public int NumberOfMoments => reference.NumberOfMoments; public void Add(OverwatchMoment moment) { if (moment.Utc < reference.EarliestUtc) reference.EarliestUtc = moment.Utc; if (moment.Utc > reference.LatestUtc) reference.LatestUtc = moment.Utc; reference.NumberOfMoments++; reference.NumberOfEvents += moment.Events.Length; var filePath = Path.Combine(workingDir, reference.MomentsFile); queue.Add(() => { File.AppendAllLines(filePath, new[] { Json.Serialize(moment) }); }); } public OverwatchMomentReference Build() { queue.StopAndJoin(); log.Debug($"Created reference with {reference.NumberOfMoments} moments and {reference.NumberOfEvents} events..."); var result = reference; reference = null!; return result; } } } }