2
0
mirror of synced 2025-01-11 17:14:25 +00:00

Upgrade reader with moment-handler

This commit is contained in:
benbierens 2024-07-31 11:02:09 +02:00
parent cfd635146b
commit 488fbb383b
No known key found for this signature in database
GPG Key ID: 877D2C2E09A22F3A
4 changed files with 161 additions and 58 deletions

View File

@ -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; }

View File

@ -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; }
}
}

View File

@ -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)

View File

@ -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; }
}
}