mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Add way for content to write arbitrary files into replay. (#5405)
Added a new RecordingStopped2 event that receives a IReplayFileWriter object that can be used to write arbitrary files into the replay zip file. Fixes #5261
This commit is contained in:
committed by
GitHub
parent
903041dfd1
commit
12b0bc4e0a
@@ -2,6 +2,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -71,8 +72,17 @@ public interface IReplayRecordingManager
|
||||
/// This gets invoked whenever a replay recording is stopping. Subscribers can use this to add extra yaml data to the
|
||||
/// recording's metadata file.
|
||||
/// </summary>
|
||||
/// <seealso cref="RecordingStopped2"/>
|
||||
event Action<MappingDataNode> RecordingStopped;
|
||||
|
||||
/// <summary>
|
||||
/// This gets invoked whenever a replay recording is stopping. Subscribers can use this to add extra data to the replay.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is effectively a more powerful version of <see cref="RecordingStopped"/>.
|
||||
/// </remarks>
|
||||
event Action<ReplayRecordingStopped> RecordingStopped2;
|
||||
|
||||
/// <summary>
|
||||
/// This gets invoked after a replay recording has finished and provides information about where the replay data
|
||||
/// was saved. Note that this only means that all write tasks have started, however some of the file tasks may not
|
||||
@@ -131,6 +141,27 @@ public interface IReplayRecordingManager
|
||||
bool IsWriting();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event object used by <see cref="IReplayRecordingManager.RecordingStopped2"/>.
|
||||
/// Allows modifying metadata and adding more data to replay files.
|
||||
/// </summary>
|
||||
public sealed class ReplayRecordingStopped
|
||||
{
|
||||
/// <summary>
|
||||
/// Mutable metadata that will be saved to the replay's metadata file.
|
||||
/// </summary>
|
||||
public required MappingDataNode Metadata { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// A writer that allows arbitrary file writing into the replay file.
|
||||
/// </summary>
|
||||
public required IReplayFileWriter Writer { get; init; }
|
||||
|
||||
internal ReplayRecordingStopped()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event data for <see cref="IReplayRecordingManager.RecordingFinished"/>.
|
||||
/// </summary>
|
||||
@@ -148,6 +179,31 @@ public record ReplayRecordingFinished(IWritableDirProvider Directory, ResPath Pa
|
||||
/// <param name="UncompressedSize">The total uncompressed size of the replay data blobs.</param>
|
||||
public record struct ReplayRecordingStats(TimeSpan Time, uint Ticks, long Size, long UncompressedSize);
|
||||
|
||||
/// <summary>
|
||||
/// Allows writing extra files directly into the replay file.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReplayRecordingStopped"/>
|
||||
/// <seealso cref="IReplayRecordingManager.RecordingStopped2"/>
|
||||
public interface IReplayFileWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The base directory inside the replay directory you should generally be writing to.
|
||||
/// This is equivalent to <see cref="ReplayConstants.ReplayZipFolder"/>.
|
||||
/// </summary>
|
||||
ResPath BaseReplayPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes arbitrary data into a file in the replay.
|
||||
/// </summary>
|
||||
/// <param name="path">The file path to write to.</param>
|
||||
/// <param name="bytes">The bytes to write to the file.</param>
|
||||
/// <param name="compressionLevel">How much to compress the file.</param>
|
||||
void WriteBytes(
|
||||
ResPath path,
|
||||
ReadOnlyMemory<byte> bytes,
|
||||
CompressionLevel compressionLevel = CompressionLevel.Optimal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Engine-internal functions for <see cref="IReplayRecordingManager"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -49,6 +49,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
|
||||
public event Action<MappingDataNode, List<object>>? RecordingStarted;
|
||||
public event Action<MappingDataNode>? RecordingStopped;
|
||||
public event Action<ReplayRecordingStopped>? RecordingStopped2;
|
||||
public event Action<ReplayRecordingFinished>? RecordingFinished;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
@@ -312,6 +313,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
|
||||
// File stream & compression context is always disposed from the worker task.
|
||||
_recState.WriteCommandChannel.Complete();
|
||||
_recState.Done = true;
|
||||
|
||||
_recState = null;
|
||||
}
|
||||
@@ -373,6 +375,11 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
{
|
||||
var yamlMetadata = new MappingDataNode();
|
||||
RecordingStopped?.Invoke(yamlMetadata);
|
||||
RecordingStopped2?.Invoke(new ReplayRecordingStopped
|
||||
{
|
||||
Metadata = yamlMetadata,
|
||||
Writer = new ReplayFileWriter(this, recState)
|
||||
});
|
||||
var time = Timing.CurTime - recState.StartTime;
|
||||
yamlMetadata[MetaFinalKeyEndTick] = new ValueDataNode(Timing.CurTick.Value.ToString());
|
||||
yamlMetadata[MetaFinalKeyDuration] = new ValueDataNode(time.ToString());
|
||||
@@ -384,6 +391,7 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
// this just overwrites the previous yml with additional data.
|
||||
var document = new YamlDocument(yamlMetadata.ToYaml());
|
||||
WriteYaml(recState, ReplayZipFolder / FileMetaFinal, document);
|
||||
|
||||
UpdateWriteTasks();
|
||||
Reset();
|
||||
|
||||
@@ -492,6 +500,8 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
public long CompressedSize;
|
||||
public long UncompressedSize;
|
||||
|
||||
public bool Done;
|
||||
|
||||
public RecordingState(
|
||||
ZipArchive zip,
|
||||
MemoryStream buffer,
|
||||
@@ -518,4 +528,23 @@ internal abstract partial class SharedReplayRecordingManager : IReplayRecordingM
|
||||
WriteCommandChannel = writeCommandChannel;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReplayFileWriter(SharedReplayRecordingManager manager, RecordingState state)
|
||||
: IReplayFileWriter
|
||||
{
|
||||
public ResPath BaseReplayPath => ReplayZipFolder;
|
||||
|
||||
public void WriteBytes(ResPath path, ReadOnlyMemory<byte> bytes, CompressionLevel compressionLevel)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
manager.WriteBytes(state, path, bytes, compressionLevel);
|
||||
}
|
||||
|
||||
private void CheckDisposed()
|
||||
{
|
||||
if (state.Done)
|
||||
throw new ObjectDisposedException(nameof(ReplayFileWriter));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user