Upgrade reader with moment-handler
This commit is contained in:
parent
cfd635146b
commit
488fbb383b
@ -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; }
|
||||
|
@ -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<T>(string key);
|
||||
void AddHandler<T>(Action<DateTime, T> handler);
|
||||
(DateTime, long)? Next();
|
||||
TimeSpan? GetDuration();
|
||||
void AddMomentHandler(Action<ActivateMoment> handler);
|
||||
void AddEventHandler<T>(Action<ActivateEvent<T>> 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<string, Action<DateTime, string>> handlers = new Dictionary<string, Action<DateTime, string>>();
|
||||
private readonly List<Action<ActivateMoment>> momentHandlers = new List<Action<ActivateMoment>>();
|
||||
private readonly Dictionary<string, List<Action<ActivateMoment, string>>> eventHandlers = new Dictionary<string, List<Action<ActivateMoment, string>>>();
|
||||
private readonly string workingDir;
|
||||
private OverwatchTranscript model = null!;
|
||||
private long momentIndex = 0;
|
||||
@ -52,44 +58,49 @@ namespace OverwatchTranscript
|
||||
return JsonConvert.DeserializeObject<T>(value)!;
|
||||
}
|
||||
|
||||
public void AddHandler<T>(Action<DateTime, T> handler)
|
||||
public void AddMomentHandler(Action<ActivateMoment> handler)
|
||||
{
|
||||
CheckClosed();
|
||||
lock (handlersLock)
|
||||
{
|
||||
momentHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddEventHandler<T>(Action<ActivateEvent<T>> 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<T>(s)!);
|
||||
});
|
||||
if (eventHandlers.ContainsKey(typeName))
|
||||
{
|
||||
eventHandlers[typeName].Add(CreateEventAction(handler));
|
||||
}
|
||||
else
|
||||
{
|
||||
eventHandlers.Add(typeName, new List<Action<ActivateMoment, string>>
|
||||
{
|
||||
CreateEventAction(handler)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publishes the events at the next moment in time. Returns that moment.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time from the current moment to the next one.
|
||||
/// </summary>
|
||||
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<ActivateMoment, string> CreateEventAction<T>(Action<ActivateEvent<T>> handler)
|
||||
{
|
||||
foreach (var @event in moment.Events)
|
||||
return (m, s) =>
|
||||
{
|
||||
PlayEvent(moment.Utc, @event);
|
||||
handler(new ActivateEvent<T>(m, JsonConvert.DeserializeObject<T>(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<T>
|
||||
{
|
||||
public ActivateEvent(ActivateMoment moment, T payload)
|
||||
{
|
||||
Moment = moment;
|
||||
Payload = payload;
|
||||
}
|
||||
|
||||
public ActivateMoment Moment { get; }
|
||||
public T Payload { get; }
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<TestHeader>(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<MyEvent>();
|
||||
reader.AddHandler<MyEvent>((utc, e) =>
|
||||
{
|
||||
e.CheckUtc = utc;
|
||||
events.Add(e);
|
||||
});
|
||||
var moments = new List<ActivateMoment>();
|
||||
var events = new List<ActivateEvent<MyEvent>>();
|
||||
reader.AddMomentHandler(moments.Add);
|
||||
reader.AddEventHandler<MyEvent>(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<MyEvent> 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; }
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user