cs-codex-dist-tests/Framework/OverwatchTranscript/TranscriptReader.cs

265 lines
7.6 KiB
C#
Raw Permalink Normal View History

2024-07-25 13:12:25 +00:00
using Newtonsoft.Json;
2024-07-31 09:02:09 +00:00
using System.IO;
using System;
2024-07-25 13:12:25 +00:00
using System.IO.Compression;
2024-07-31 09:02:09 +00:00
using System.Linq;
using System.Collections.Generic;
using System.Collections.Concurrent;
2024-07-25 13:12:25 +00:00
namespace OverwatchTranscript
{
2024-07-25 14:00:51 +00:00
public interface ITranscriptReader
{
2024-07-30 13:42:51 +00:00
OverwatchCommonHeader Header { get; }
2024-07-25 14:00:51 +00:00
T GetHeader<T>(string key);
2024-07-31 09:02:09 +00:00
void AddMomentHandler(Action<ActivateMoment> handler);
void AddEventHandler<T>(Action<ActivateEvent<T>> handler);
bool Next();
2024-07-25 14:00:51 +00:00
void Close();
}
public class TranscriptReader : ITranscriptReader
2024-07-25 13:12:25 +00:00
{
2024-07-31 09:02:09 +00:00
private readonly object handlersLock = new object();
2024-07-25 13:12:25 +00:00
private readonly string transcriptFile;
private readonly string artifactsFolder;
2024-07-31 09:02:09 +00:00
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>>>();
2024-07-25 13:12:25 +00:00
private readonly string workingDir;
2024-08-01 14:25:28 +00:00
private readonly OverwatchTranscript model;
2024-07-25 13:12:25 +00:00
private bool closed;
2024-08-01 14:25:28 +00:00
private long momentCounter;
private readonly ConcurrentQueue<OverwatchMoment> queue = new ConcurrentQueue<OverwatchMoment>();
2024-08-02 06:56:49 +00:00
private readonly Task queueFiller;
2024-07-25 13:12:25 +00:00
public TranscriptReader(string workingDir, string inputFilename)
{
closed = false;
this.workingDir = workingDir;
transcriptFile = Path.Combine(workingDir, TranscriptConstants.TranscriptFilename);
artifactsFolder = Path.Combine(workingDir, TranscriptConstants.ArtifactFolderName);
if (!Directory.Exists(workingDir)) Directory.CreateDirectory(workingDir);
if (File.Exists(transcriptFile) || Directory.Exists(artifactsFolder)) throw new Exception("workingdir not clean");
2024-08-01 14:25:28 +00:00
model = LoadModel(inputFilename);
2024-08-02 06:56:49 +00:00
queueFiller = Task.Run(() => FillQueue(model, workingDir));
2024-07-25 13:12:25 +00:00
}
2024-07-29 06:08:17 +00:00
public OverwatchCommonHeader Header
{
get
{
2024-07-30 13:42:51 +00:00
CheckClosed();
2024-07-29 06:08:17 +00:00
return model.Header.Common;
}
}
2024-07-25 13:12:25 +00:00
public T GetHeader<T>(string key)
{
CheckClosed();
var value = model.Header.Entries.First(e => e.Key == key).Value;
return JsonConvert.DeserializeObject<T>(value)!;
}
2024-07-31 09:02:09 +00:00
public void AddMomentHandler(Action<ActivateMoment> handler)
2024-07-25 13:12:25 +00:00
{
CheckClosed();
2024-07-31 09:02:09 +00:00
lock (handlersLock)
{
momentHandlers.Add(handler);
}
}
public void AddEventHandler<T>(Action<ActivateEvent<T>> handler)
{
CheckClosed();
2024-07-25 13:12:25 +00:00
var typeName = typeof(T).FullName;
if (string.IsNullOrEmpty(typeName)) throw new Exception("Empty typename for payload");
2024-07-31 09:02:09 +00:00
lock (handlersLock)
2024-07-25 13:12:25 +00:00
{
2024-07-31 09:02:09 +00:00
if (eventHandlers.ContainsKey(typeName))
{
eventHandlers[typeName].Add(CreateEventAction(handler));
}
else
{
eventHandlers.Add(typeName, new List<Action<ActivateMoment, string>>
{
CreateEventAction(handler)
});
}
}
2024-07-25 13:12:25 +00:00
}
private readonly object nextLock = new object();
private OverwatchMoment? moment = null;
private OverwatchMoment? next = null;
public bool Next()
2024-07-25 13:12:25 +00:00
{
CheckClosed();
OverwatchMoment? m = null;
TimeSpan? duration = null;
lock (nextLock)
2024-08-01 14:25:28 +00:00
{
if (next == null)
{
if (!queue.TryDequeue(out moment)) return false;
queue.TryDequeue(out next);
}
else
{
moment = next;
next = null;
queue.TryDequeue(out next);
}
2024-08-02 06:56:49 +00:00
m = moment;
duration = GetMomentDuration();
2024-08-01 14:25:28 +00:00
}
2024-07-31 09:02:09 +00:00
2024-08-02 06:56:49 +00:00
ActivateMoment(moment, duration);
return true;
2024-07-31 09:02:09 +00:00
}
2024-07-25 13:12:25 +00:00
2024-07-31 09:02:09 +00:00
public void Close()
{
CheckClosed();
closed = true;
2024-08-02 06:56:49 +00:00
queueFiller.Wait();
Directory.Delete(workingDir, true);
2024-07-30 13:42:51 +00:00
}
2024-07-31 09:02:09 +00:00
private Action<ActivateMoment, string> CreateEventAction<T>(Action<ActivateEvent<T>> handler)
2024-07-30 13:42:51 +00:00
{
2024-07-31 09:02:09 +00:00
return (m, s) =>
{
handler(new ActivateEvent<T>(m, JsonConvert.DeserializeObject<T>(s)!));
};
}
private void FillQueue(OverwatchTranscript model, string workingDir)
2024-07-31 09:02:09 +00:00
{
var reader = new MomentReader(model, workingDir);
2024-08-02 06:56:49 +00:00
while (true)
{
if (closed)
{
reader.Close();
return;
}
2024-08-02 06:56:49 +00:00
while (queue.Count < 10)
2024-08-02 06:56:49 +00:00
{
var moment = reader.Next();
if (moment == null)
2024-08-02 06:56:49 +00:00
{
reader.Close();
return;
2024-08-02 06:56:49 +00:00
}
queue.Enqueue(moment);
2024-08-02 06:56:49 +00:00
}
Thread.Sleep(1);
}
}
private TimeSpan? GetMomentDuration()
2024-08-02 06:56:49 +00:00
{
if (moment == null) return null;
2024-08-01 14:25:28 +00:00
if (next == null) return null;
2024-07-30 13:42:51 +00:00
2024-08-02 06:56:49 +00:00
return next.Utc - moment.Utc;
2024-07-25 13:12:25 +00:00
}
2024-08-02 06:56:49 +00:00
private void ActivateMoment(OverwatchMoment moment, TimeSpan? duration)
2024-07-25 13:12:25 +00:00
{
2024-08-02 06:56:49 +00:00
var m = new ActivateMoment(moment.Utc, duration, momentCounter);
2024-07-31 09:02:09 +00:00
lock (handlersLock)
{
ActivateMomentHandlers(m);
foreach (var @event in moment.Events)
{
ActivateEventHandlers(m, @event);
}
}
2024-08-02 06:56:49 +00:00
momentCounter++;
2024-07-25 13:12:25 +00:00
}
2024-07-31 09:02:09 +00:00
private void ActivateMomentHandlers(ActivateMoment m)
2024-07-26 08:56:22 +00:00
{
2024-07-31 09:02:09 +00:00
foreach (var handler in momentHandlers)
2024-07-26 08:56:22 +00:00
{
2024-07-31 09:02:09 +00:00
handler(m);
2024-07-26 08:56:22 +00:00
}
}
2024-07-31 09:02:09 +00:00
private void ActivateEventHandlers(ActivateMoment m, OverwatchEvent @event)
2024-07-25 13:12:25 +00:00
{
2024-07-31 09:02:09 +00:00
if (!eventHandlers.ContainsKey(@event.Type)) return;
var handlers = eventHandlers[@event.Type];
2024-07-25 13:12:25 +00:00
2024-07-31 09:02:09 +00:00
foreach (var handler in handlers)
{
handler(m, @event.Payload);
}
2024-07-25 13:12:25 +00:00
}
2024-08-01 14:25:28 +00:00
private OverwatchTranscript LoadModel(string inputFilename)
2024-07-25 13:12:25 +00:00
{
ZipFile.ExtractToDirectory(inputFilename, workingDir);
if (!File.Exists(transcriptFile))
{
closed = true;
throw new Exception("Is not a transcript file. Unzipped to: " + workingDir);
}
2024-08-01 14:25:28 +00:00
return JsonConvert.DeserializeObject<OverwatchTranscript>(File.ReadAllText(transcriptFile))!;
2024-07-25 13:12:25 +00:00
}
private void CheckClosed()
{
2024-07-30 13:42:51 +00:00
if (closed) throw new Exception("Transcript has already been closed.");
2024-07-25 13:12:25 +00:00
}
}
2024-07-31 09:02:09 +00:00
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; }
}
2024-07-25 13:12:25 +00:00
}