Skip to content

Commit 50a6b08

Browse files
committed
Replay plugin back in a kinda working state, still not 100% done though
1 parent 673cede commit 50a6b08

File tree

10 files changed

+331
-180
lines changed

10 files changed

+331
-180
lines changed
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
using AssettoServer.Server.Ai;
22
using AssettoServer.Shared.Model;
3-
using Microsoft.Extensions.ObjectPool;
43

54
namespace ReplayPlugin.Data;
65

7-
public class ReplayFrameState : IResettable
6+
public class ReplayFrameState
87
{
98
public readonly List<ValueTuple<byte, CarStatus>> PlayerCars = [];
109
public readonly List<ValueTuple<byte, CarStatus>> AiCars = [];
1110
public readonly Dictionary<AiState, short> AiStateMapping = [];
1211
public readonly Dictionary<byte, List<short>> AiFrameMapping = [];
1312

14-
public bool TryReset()
13+
public void Reset()
1514
{
1615
PlayerCars.Clear();
1716
AiCars.Clear();
1817
AiStateMapping.Clear();
1918
AiFrameMapping.Clear();
20-
21-
return true;
2219
}
2320
}

ReplayPlugin/Data/ReplaySegment.cs

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,62 +6,78 @@
66

77
namespace ReplayPlugin.Data;
88

9-
public class ReplaySegment : IDisposable
9+
public sealed class ReplaySegment : IDisposable
1010
{
11-
public long StartTime;
12-
public long EndTime;
13-
public uint StartPlayerInfoIndex;
14-
public uint EndPlayerInfoIndex;
11+
public long StartTime { get; private set; }
12+
public long EndTime { get; private set; }
13+
public uint StartPlayerInfoIndex { get; private set; }
14+
public uint EndPlayerInfoIndex { get; private set; }
15+
public List<int> Index { get; } = [];
16+
public int Size { get; private set; }
17+
public bool IsBusy => _lockCount > 0;
1518

1619
private readonly string _path;
1720
private readonly int _size;
1821
private MemoryMappedFile? _file;
1922
private IMappedMemory? _fileAccessor;
2023
private Memory<byte>? _memory;
24+
private int _lockCount;
25+
private bool _isDisposed;
2126

22-
public readonly List<int> Index = [];
23-
public int Size { get; private set; }
24-
27+
[MemberNotNullWhen(true, nameof(_file), nameof(_fileAccessor), nameof(_memory))]
28+
private bool IsLoaded => _memory != null && _fileAccessor != null && _file != null;
29+
2530
public ReplaySegment(string path, int size)
2631
{
2732
_path = path;
2833
_size = size;
2934
Load();
3035
}
3136

37+
public ReplaySegmentLock KeepLoaded()
38+
{
39+
return new ReplaySegmentLock(this);
40+
}
41+
3242
[MemberNotNull(nameof(_file), nameof(_fileAccessor), nameof(_memory))]
3343
private void Load()
3444
{
35-
Log.Debug("Loading replay segment {0}", _path);
45+
if (IsLoaded) return;
46+
47+
Log.Debug("Loading replay segment {Path}", _path);
3648
_file = MemoryMappedFile.CreateFromFile(_path, FileMode.OpenOrCreate, null, _size, MemoryMappedFileAccess.ReadWrite);
3749
_fileAccessor = _file.CreateMemoryAccessor();
3850
_memory = _fileAccessor.Memory;
3951
}
40-
41-
public void Unload()
52+
53+
private bool TryUnload()
4254
{
43-
Log.Debug("Unloading replay segment {0}", _path);
55+
if (_lockCount > 0) return false;
56+
57+
Log.Debug("Unloading replay segment {Path}", _path);
4458
_memory = null;
4559
_fileAccessor?.Dispose();
4660
_fileAccessor = null;
4761
_file?.Dispose();
4862
_file = null;
63+
return true;
4964
}
5065

5166
[MemberNotNull(nameof(_file), nameof(_fileAccessor), nameof(_memory))]
52-
private void EnsureLoaded()
67+
private void ThrowIfUnloaded()
5368
{
54-
if (_memory == null || _fileAccessor == null || _file == null)
69+
ObjectDisposedException.ThrowIf(_isDisposed, this);
70+
if (!IsLoaded)
5571
{
56-
Load();
72+
throw new InvalidOperationException("Replay segment is not loaded");
5773
}
5874
}
5975

6076
public delegate void ReplayFrameAction<in TState>(ref ReplayFrame frame, TState arg);
6177

62-
public bool TryAddFrame<TState>(int numCarFrames, int numAiFrames, int numAiMappings, uint playerInfoIndex, TState state, [RequireStaticDelegate] ReplayFrameAction<TState> action)
78+
public bool TryAddFrame<TState>(int numCarFrames, int numAiFrames, int numAiMappings, uint playerInfoIndex, TState state, [RequireStaticDelegate, InstantHandle] ReplayFrameAction<TState> action)
6379
{
64-
EnsureLoaded();
80+
ThrowIfUnloaded();
6581

6682
var size = ReplayFrame.GetSize(numCarFrames, numAiFrames, numAiMappings);
6783

@@ -105,16 +121,51 @@ public ReplayFrame Current
105121
{
106122
get
107123
{
108-
segment.EnsureLoaded();
124+
segment.ThrowIfUnloaded();
109125
return new ReplayFrame(segment._memory.Value[segment.Index[_i]..]);
110126
}
111127
}
112128
}
113129

114130
public void Dispose()
115131
{
116-
Log.Debug("Disposing replay segment {0}", _path);
117-
Unload();
118-
File.Delete(_path);
132+
if (_isDisposed) return;
133+
134+
if (TryUnload())
135+
{
136+
Log.Debug("Disposing replay segment {SegmentPath}", _path);
137+
File.Delete(_path);
138+
}
139+
else
140+
{
141+
Log.Error("Cannot dispose and delete replay segment {SegmentPath} because it is locked", _path);
142+
}
143+
144+
_isDisposed = true;
145+
}
146+
147+
public sealed class ReplaySegmentLock : IDisposable
148+
{
149+
private readonly ReplaySegment _segment;
150+
private bool _isDisposed;
151+
152+
public ReplaySegmentLock(ReplaySegment segment)
153+
{
154+
_segment = segment;
155+
Interlocked.Increment(ref _segment._lockCount);
156+
_segment.Load();
157+
}
158+
159+
~ReplaySegmentLock() => Dispose();
160+
161+
public void Dispose()
162+
{
163+
if (_isDisposed) return;
164+
Interlocked.Decrement(ref _segment._lockCount);
165+
_segment.TryUnload();
166+
167+
_isDisposed = true;
168+
GC.SuppressFinalize(this);
169+
}
119170
}
120171
}

ReplayPlugin/ReplayCommandModule.cs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22
using AssettoServer.Commands.Attributes;
33
using AssettoServer.Network.Tcp;
44
using Qmmands;
5-
using SerilogTimings;
65

76
namespace ReplayPlugin;
87

98
[RequireAdmin]
109
public class ReplayCommandModule : ACModuleBase
1110
{
12-
private readonly ReplayManager _replayManager;
11+
private readonly ReplayService _replayService;
1312

14-
public ReplayCommandModule(ReplayManager replayManager)
13+
public ReplayCommandModule(ReplayService replayService)
1514
{
16-
_replayManager = replayManager;
15+
_replayService = replayService;
1716
}
1817

1918
[Command("replay")]
20-
public void SaveReplay(int seconds, [Remainder] ACTcpClient? client = null)
19+
public async Task SaveReplayAsync(int seconds, [Remainder] ACTcpClient? client = null)
2120
{
2221
var sessionId = client?.SessionId ?? Client?.SessionId;
2322

@@ -27,18 +26,14 @@ public void SaveReplay(int seconds, [Remainder] ACTcpClient? client = null)
2726
return;
2827
}
2928

30-
SaveReplayId(seconds, sessionId.Value);
29+
await SaveReplayIdAsync(seconds, sessionId.Value);
3130
}
3231

3332
[Command("replay_id")]
34-
public void SaveReplayId(int seconds, byte sessionId)
33+
public async Task SaveReplayIdAsync(int seconds, byte sessionId)
3534
{
3635
var filename = $"replay_{DateTime.Now:yyyyMMdd'T'HHmmss}_{sessionId}.acreplay";
37-
38-
using (var t = Operation.Time("Writing replay {0}", filename))
39-
{
40-
_replayManager.WriteReplay(seconds, sessionId, filename);
41-
}
36+
await _replayService.SaveReplayAsync(seconds, sessionId, filename);
4237
Reply($"Saved replay {filename}");
4338
}
4439
}

ReplayPlugin/ReplayModule.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
using AssettoServer.Server.Plugin;
22
using Autofac;
3+
using Microsoft.Extensions.Hosting;
34

45
namespace ReplayPlugin;
56

67
public class ReplayModule : AssettoServerModule<ReplayConfiguration>
78
{
89
protected override void Load(ContainerBuilder builder)
910
{
10-
builder.RegisterType<ReplayPlugin>().AsSelf().As<IAssettoServerAutostart>().SingleInstance();
11-
builder.RegisterType<ReplayManager>().AsSelf().SingleInstance();
11+
builder.RegisterType<ReplayPlugin>().AsSelf().As<IHostedService>().SingleInstance();
12+
builder.RegisterType<ReplayService>().AsSelf().As<IHostedService>().SingleInstance();
13+
builder.RegisterType<ReplayWriter>().AsSelf().SingleInstance();
14+
builder.RegisterType<ReplaySegmentManager>().AsSelf().SingleInstance();
1215
builder.RegisterType<EntryCarExtraDataManager>().AsSelf().SingleInstance();
1316
builder.RegisterType<ReplayMetadataProvider>().AsSelf().SingleInstance();
1417
}

ReplayPlugin/ReplayPlugin.cs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
using System.Reflection;
2-
using System.Runtime.InteropServices;
32
using AssettoServer.Network.Tcp;
43
using AssettoServer.Server;
5-
using AssettoServer.Server.Plugin;
64
using AssettoServer.Server.Weather;
7-
using AssettoServer.Shared.Services;
85
using AssettoServer.Shared.Weather;
96
using AssettoServer.Utils;
107
using Microsoft.Extensions.Hosting;
@@ -15,42 +12,39 @@
1512

1613
namespace ReplayPlugin;
1714

18-
public class ReplayPlugin : CriticalBackgroundService, IAssettoServerAutostart
15+
public class ReplayPlugin : BackgroundService
1916
{
2017
private readonly ReplayConfiguration _configuration;
2118
private readonly EntryCarManager _entryCarManager;
2219
private readonly SessionManager _session;
2320
private readonly WeatherManager _weather;
24-
private readonly ReplayManager _replayManager;
21+
private readonly ReplaySegmentManager _replaySegmentManager;
2522
private readonly Summary _onUpdateTimer;
2623
private readonly EntryCarExtraDataManager _extraData;
2724
private readonly ReplayMetadataProvider _metadata;
2825

29-
public ReplayPlugin(IHostApplicationLifetime applicationLifetime,
30-
EntryCarManager entryCarManager,
26+
public ReplayPlugin(EntryCarManager entryCarManager,
3127
WeatherManager weather,
3228
SessionManager session,
33-
ReplayManager replayManager,
29+
ReplaySegmentManager replaySegmentManager,
3430
ReplayConfiguration configuration,
3531
CSPServerScriptProvider scriptProvider,
3632
CSPClientMessageTypeManager cspClientMessageTypeManager,
3733
EntryCarExtraDataManager extraData,
38-
ReplayMetadataProvider metadata) : base(applicationLifetime)
34+
ReplayMetadataProvider metadata)
3935
{
4036
_entryCarManager = entryCarManager;
4137
_weather = weather;
4238
_session = session;
43-
_replayManager = replayManager;
39+
_replaySegmentManager = replaySegmentManager;
4440
_configuration = configuration;
4541
_extraData = extraData;
4642
_metadata = metadata;
4743

4844
_onUpdateTimer = Metrics.CreateSummary("assettoserver_replayplugin_onupdate", "ReplayPlugin.OnUpdate Duration", MetricDefaults.DefaultQuantiles);
4945

5046
cspClientMessageTypeManager.RegisterOnlineEvent<UploadDataPacket>(OnUploadData);
51-
52-
using var streamReader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("ReplayPlugin.lua.replay.lua")!);
53-
scriptProvider.AddScript(streamReader.ReadToEnd(), "replay.lua");
47+
scriptProvider.AddScript(Assembly.GetExecutingAssembly().GetManifestResourceStream("ReplayPlugin.lua.replay.lua")!, "replay.lua");
5448
}
5549

5650
private void OnUploadData(ACTcpClient sender, UploadDataPacket packet)
@@ -65,15 +59,15 @@ private void Update()
6559
{
6660
using var timer = _onUpdateTimer.NewTimer();
6761

68-
_state.TryReset();
62+
_state.Reset();
6963

7064
int numAiMappings = 0;
7165
foreach (var entryCar in _entryCarManager.EntryCars)
7266
{
7367
if (entryCar.Client?.HasSentFirstUpdate == true)
7468
{
7569
_state.PlayerCars.Add((entryCar.SessionId, entryCar.Status));
76-
_state.AiFrameMapping.Add(entryCar.SessionId, new List<short>());
70+
_state.AiFrameMapping.Add(entryCar.SessionId, []);
7771
numAiMappings++;
7872
}
7973
else if (entryCar.AiControlled)
@@ -99,7 +93,7 @@ private void Update()
9993
}
10094
}
10195

102-
_replayManager.AddFrame(_state.PlayerCars.Count, _state.AiCars.Count, numAiMappings, _metadata.Index, this, WriteFrame);
96+
_replaySegmentManager.AddFrame(_state.PlayerCars.Count, _state.AiCars.Count, numAiMappings, _metadata.Index, this, WriteFrame);
10397
}
10498

10599
private static void WriteFrame(ref ReplayFrame frame, ReplayPlugin self)
@@ -132,8 +126,6 @@ private static void WriteFrame(ref ReplayFrame frame, ReplayPlugin self)
132126

133127
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
134128
{
135-
Log.Debug("ReplayCarFrame size {Size} bytes", Marshal.SizeOf<ReplayCarFrame>());
136-
137129
_extraData.Initialize(_entryCarManager.EntryCars.Length);
138130
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(1000.0 / _configuration.RefreshRateHz));
139131
while (await timer.WaitForNextTickAsync(stoppingToken))

0 commit comments

Comments
 (0)