From 488fbb383b82cdb7147f26a6b0a9760fc45082ec Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 31 Jul 2024 11:02:09 +0200 Subject: [PATCH] Upgrade reader with moment-handler --- Framework/OverwatchTranscript/Model.cs | 1 + .../OverwatchTranscript/TranscriptReader.cs | 145 +++++++++++++----- .../OverwatchTranscript/TranscriptWriter.cs | 1 + .../OverwatchTranscript/TranscriptTests.cs | 72 ++++++--- 4 files changed, 161 insertions(+), 58 deletions(-) diff --git a/Framework/OverwatchTranscript/Model.cs b/Framework/OverwatchTranscript/Model.cs index db3f8e2..2931850 100644 --- a/Framework/OverwatchTranscript/Model.cs +++ b/Framework/OverwatchTranscript/Model.cs @@ -17,6 +17,7 @@ [Serializable] public class OverwatchCommonHeader { + public long NumberOfMoments { get; set; } public long NumberOfEvents { get; set; } public DateTime EarliestUct { get; set; } public DateTime LatestUtc { get; set; } diff --git a/Framework/OverwatchTranscript/TranscriptReader.cs b/Framework/OverwatchTranscript/TranscriptReader.cs index 645c078..ddfaec3 100644 --- a/Framework/OverwatchTranscript/TranscriptReader.cs +++ b/Framework/OverwatchTranscript/TranscriptReader.cs @@ -1,5 +1,9 @@ using Newtonsoft.Json; +using System.IO; +using System; using System.IO.Compression; +using System.Linq; +using System.Collections.Generic; namespace OverwatchTranscript { @@ -7,17 +11,19 @@ namespace OverwatchTranscript { OverwatchCommonHeader Header { get; } T GetHeader(string key); - void AddHandler(Action handler); - (DateTime, long)? Next(); - TimeSpan? GetDuration(); + void AddMomentHandler(Action handler); + void AddEventHandler(Action> handler); + void Next(); void Close(); } public class TranscriptReader : ITranscriptReader { + private readonly object handlersLock = new object(); private readonly string transcriptFile; private readonly string artifactsFolder; - private readonly Dictionary> handlers = new Dictionary>(); + private readonly List> momentHandlers = new List>(); + private readonly Dictionary>> eventHandlers = new Dictionary>>(); private readonly string workingDir; private OverwatchTranscript model = null!; private long momentIndex = 0; @@ -52,44 +58,49 @@ namespace OverwatchTranscript return JsonConvert.DeserializeObject(value)!; } - public void AddHandler(Action handler) + public void AddMomentHandler(Action handler) { CheckClosed(); + lock (handlersLock) + { + momentHandlers.Add(handler); + } + } + + public void AddEventHandler(Action> handler) + { + CheckClosed(); + var typeName = typeof(T).FullName; if (string.IsNullOrEmpty(typeName)) throw new Exception("Empty typename for payload"); - handlers.Add(typeName, (utc, s) => + lock (handlersLock) { - handler(utc, JsonConvert.DeserializeObject(s)!); - }); + if (eventHandlers.ContainsKey(typeName)) + { + eventHandlers[typeName].Add(CreateEventAction(handler)); + } + else + { + eventHandlers.Add(typeName, new List> + { + CreateEventAction(handler) + }); + } + } } - /// - /// Publishes the events at the next moment in time. Returns that moment. - /// - public (DateTime, long)? Next() + public void Next() { CheckClosed(); - if (momentIndex >= model.Moments.Length) return null; + if (momentIndex >= model.Moments.Length) return; var moment = model.Moments[momentIndex]; + var momentDuration = GetMomentDuration(); + + ActivateMoment(moment, momentDuration, momentIndex); + momentIndex++; - - PlayMoment(moment); - return (moment.Utc, momentIndex); - } - - /// - /// Gets the time from the current moment to the next one. - /// - public TimeSpan? GetDuration() - { - if (momentIndex - 1 < 0) return null; - if (momentIndex >= model.Moments.Length) return null; - - return - model.Moments[momentIndex].Utc - - model.Moments[momentIndex - 1].Utc; } public void Close() @@ -99,20 +110,56 @@ namespace OverwatchTranscript closed = true; } - private void PlayMoment(OverwatchMoment moment) + private Action CreateEventAction(Action> handler) { - foreach (var @event in moment.Events) + return (m, s) => { - PlayEvent(moment.Utc, @event); + handler(new ActivateEvent(m, JsonConvert.DeserializeObject(s)!)); + }; + } + + private TimeSpan? GetMomentDuration() + { + if (momentIndex < 0) throw new Exception("Index < 0"); + if (momentIndex + 1 >= model.Moments.Length) return null; + + return + model.Moments[momentIndex + 1].Utc - + model.Moments[momentIndex].Utc; + } + + private void ActivateMoment(OverwatchMoment moment, TimeSpan? duration, long momentIndex) + { + var m = new ActivateMoment(moment.Utc, duration, momentIndex); + + lock (handlersLock) + { + ActivateMomentHandlers(m); + + foreach (var @event in moment.Events) + { + ActivateEventHandlers(m, @event); + } } } - private void PlayEvent(DateTime utc, OverwatchEvent @event) + private void ActivateMomentHandlers(ActivateMoment m) { - if (!handlers.ContainsKey(@event.Type)) return; - var handler = handlers[@event.Type]; + foreach (var handler in momentHandlers) + { + handler(m); + } + } - handler(utc, @event.Payload); + private void ActivateEventHandlers(ActivateMoment m, OverwatchEvent @event) + { + if (!eventHandlers.ContainsKey(@event.Type)) return; + var handlers = eventHandlers[@event.Type]; + + foreach (var handler in handlers) + { + handler(m, @event.Payload); + } } private void LoadModel(string inputFilename) @@ -133,4 +180,30 @@ namespace OverwatchTranscript if (closed) throw new Exception("Transcript has already been closed."); } } + + public class ActivateMoment + { + public ActivateMoment(DateTime utc, TimeSpan? duration, long index) + { + Utc = utc; + Duration = duration; + Index = index; + } + + public DateTime Utc { get; } + public TimeSpan? Duration { get; } + public long Index { get; } + } + + public class ActivateEvent + { + public ActivateEvent(ActivateMoment moment, T payload) + { + Moment = moment; + Payload = payload; + } + + public ActivateMoment Moment { get; } + public T Payload { get; } + } } diff --git a/Framework/OverwatchTranscript/TranscriptWriter.cs b/Framework/OverwatchTranscript/TranscriptWriter.cs index 742a771..c3ee757 100644 --- a/Framework/OverwatchTranscript/TranscriptWriter.cs +++ b/Framework/OverwatchTranscript/TranscriptWriter.cs @@ -131,6 +131,7 @@ namespace OverwatchTranscript { return new OverwatchCommonHeader { + NumberOfMoments = buffer.Count, NumberOfEvents = buffer.Sum(e => e.Value.Count), EarliestUct = buffer.Min(e => e.Key), LatestUtc = buffer.Max(e => e.Key) diff --git a/Tests/FrameworkTests/OverwatchTranscript/TranscriptTests.cs b/Tests/FrameworkTests/OverwatchTranscript/TranscriptTests.cs index 2b058c6..6aea1b9 100644 --- a/Tests/FrameworkTests/OverwatchTranscript/TranscriptTests.cs +++ b/Tests/FrameworkTests/OverwatchTranscript/TranscriptTests.cs @@ -13,9 +13,10 @@ namespace FrameworkTests.OverwatchTranscript private const string EventData0 = "12345"; private const string EventData1 = "678"; private const string EventData2 = "90"; + private const string EventData3 = "-="; private readonly DateTime t0 = DateTime.UtcNow; private readonly DateTime t1 = DateTime.UtcNow.AddMinutes(1); - private readonly DateTime t2 = DateTime.UtcNow.AddMinutes(2); + private readonly DateTime t2 = DateTime.UtcNow.AddMinutes(3); [Test] public void WriteAndRun() @@ -43,12 +44,16 @@ namespace FrameworkTests.OverwatchTranscript }); writer.Add(t2, new MyEvent { - EventData = EventData2 + EventData = EventData3 }); writer.Add(t1, new MyEvent { EventData = EventData1 }); + writer.Add(t1, new MyEvent + { + EventData = EventData2 + }); if (File.Exists(TranscriptFilename)) File.Delete(TranscriptFilename); @@ -61,36 +66,62 @@ namespace FrameworkTests.OverwatchTranscript var header = reader.GetHeader(HeaderKey); Assert.That(header.HeaderData, Is.EqualTo(HeaderData)); - Assert.That(reader.Header.NumberOfEvents, Is.EqualTo(3)); + Assert.That(reader.Header.NumberOfMoments, Is.EqualTo(3)); + Assert.That(reader.Header.NumberOfEvents, Is.EqualTo(4)); Assert.That(reader.Header.EarliestUct, Is.EqualTo(t0)); Assert.That(reader.Header.LatestUtc, Is.EqualTo(t2)); - var events = new List(); - reader.AddHandler((utc, e) => - { - e.CheckUtc = utc; - events.Add(e); - }); + var moments = new List(); + var events = new List>(); + reader.AddMomentHandler(moments.Add); + reader.AddEventHandler(events.Add); + + Assert.That(moments.Count, Is.EqualTo(0)); Assert.That(events.Count, Is.EqualTo(0)); + reader.Next(); + Assert.That(moments.Count, Is.EqualTo(1)); Assert.That(events.Count, Is.EqualTo(1)); + reader.Next(); - Assert.That(events.Count, Is.EqualTo(2)); - reader.Next(); - Assert.That(events.Count, Is.EqualTo(3)); - reader.Next(); + Assert.That(moments.Count, Is.EqualTo(2)); Assert.That(events.Count, Is.EqualTo(3)); - Assert.That(events[0].CheckUtc, Is.EqualTo(t0)); - Assert.That(events[0].EventData, Is.EqualTo(EventData0)); - Assert.That(events[1].CheckUtc, Is.EqualTo(t1)); - Assert.That(events[1].EventData, Is.EqualTo(EventData1)); - Assert.That(events[2].CheckUtc, Is.EqualTo(t2)); - Assert.That(events[2].EventData, Is.EqualTo(EventData2)); + reader.Next(); + Assert.That(moments.Count, Is.EqualTo(3)); + Assert.That(events.Count, Is.EqualTo(4)); + + reader.Next(); + Assert.That(moments.Count, Is.EqualTo(3)); + Assert.That(events.Count, Is.EqualTo(4)); + + AssertMoment(moments[0], utc: t0, duration: t1 - t0, index: 0); + AssertMoment(moments[1], utc: t1, duration: t2 - t1, index: 1); + AssertMoment(moments[2], utc: t2, duration: null, index: 2); + + AssertEvent(events[0], utc: t0, duration: t1 - t0, index: 0, data: EventData0); + AssertEvent(events[1], utc: t1, duration: t2 - t1, index: 1, data: EventData1); + AssertEvent(events[2], utc: t1, duration: t2 - t1, index: 1, data: EventData2); + AssertEvent(events[3], utc: t2, duration: null, index: 2, data: EventData3); reader.Close(); } + + private void AssertMoment(ActivateMoment m, DateTime utc, TimeSpan? duration, int index) + { + Assert.That(m.Utc, Is.EqualTo(utc)); + Assert.That(m.Duration, Is.EqualTo(duration)); + Assert.That(m.Index, Is.EqualTo(index)); + } + + private void AssertEvent(ActivateEvent e, DateTime utc, TimeSpan? duration, int index, string data) + { + Assert.That(e.Moment.Utc, Is.EqualTo(utc)); + Assert.That(e.Moment.Duration, Is.EqualTo(duration)); + Assert.That(e.Moment.Index, Is.EqualTo(index)); + Assert.That(e.Payload.EventData, Is.EqualTo(data)); + } } public class TestHeader @@ -101,8 +132,5 @@ namespace FrameworkTests.OverwatchTranscript public class MyEvent { public string EventData { get; set; } = string.Empty; - - [JsonIgnore] - public DateTime CheckUtc { get; set; } } }