mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
247 lines
9.5 KiB
C#
247 lines
9.5 KiB
C#
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.Serialization.Markdown.Mapping;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Threading.Tasks;
|
|
using Robust.Shared.ContentPack;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Serialization;
|
|
using Robust.Shared.Utility;
|
|
using YamlDotNet.Core;
|
|
using YamlDotNet.RepresentationModel;
|
|
|
|
namespace Robust.Shared.Replays;
|
|
|
|
[NotContentImplementable]
|
|
public interface IReplayRecordingManager
|
|
{
|
|
/// <summary>
|
|
/// Whether or not a replay recording can currently be started.
|
|
/// </summary>
|
|
bool CanStartRecording();
|
|
|
|
/// <summary>
|
|
/// This is a convenience variation of <see cref="RecordReplayMessage"/> that only records messages for server-side
|
|
/// recordings.
|
|
/// </summary>
|
|
void RecordServerMessage(object obj);
|
|
|
|
/// <summary>
|
|
/// This is a convenience variation of <see cref="RecordReplayMessage"/> that only records messages for client-side
|
|
/// recordings.
|
|
/// </summary>
|
|
void RecordClientMessage(object obj);
|
|
|
|
/// <summary>
|
|
/// Queues some net-serializable data to be saved by a replay recording. Does nothing if <see cref="IsRecording"/>
|
|
/// is false.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The queued object is typically something like an <see cref="EntityEventArgs"/>, so that replays can
|
|
/// simulate receiving networked messages. However, this can really be any serializable data and could be used
|
|
/// for saving server-exclusive data like power net or atmos pipe-net data for replaying. Alternatively, those
|
|
/// could also just use networked component states on entities that are in null space and hidden from all
|
|
/// players (but still saved to replays).
|
|
/// </remarks>
|
|
void RecordReplayMessage(object obj);
|
|
|
|
/// <summary>
|
|
/// Whether the server is currently recording replay data.
|
|
/// </summary>
|
|
bool IsRecording { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the <c>state</c> object passed into <see cref="TryStartRecording"/> for the current recording.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Returns <see langword="null"/> if there is no active replay recording.
|
|
/// </remarks>
|
|
public object? ActiveRecordingState { get; }
|
|
|
|
/// <summary>
|
|
/// Processes pending write tasks and saves the replay data for the current tick. This should be called even if a
|
|
/// replay is not currently being recorded.
|
|
/// </summary>
|
|
void Update(GameState? state);
|
|
|
|
/// <summary>
|
|
/// This gets invoked whenever a replay recording is starting. Subscribers can use this to add extra yaml data
|
|
/// to the recording's metadata file, as well as to provide serializable messages that get replayed when the replay
|
|
/// is initially loaded. E.g., this should contain networked events that would get sent to a newly connected client.
|
|
/// </summary>
|
|
event Action<MappingDataNode, List<object>> RecordingStarted;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// have finished yet. See <see cref="WaitWriteTasks"/>.
|
|
/// </summary>
|
|
event Action<ReplayRecordingFinished> RecordingFinished;
|
|
|
|
/// <summary>
|
|
/// Tries to starts a replay recording.
|
|
/// </summary>
|
|
/// <param name="directory">
|
|
/// The directory that the replay will be written to. E.g., <see cref="IResourceManager.UserData"/>.
|
|
/// </param>
|
|
/// <param name="name">
|
|
/// The name of the replay. This will also determine the folder where the replay will be stored, which will be a
|
|
/// subfolder within <see cref="CVars.ReplayDirectory"/>. If not provided, will default to using the current time.
|
|
/// </param>
|
|
/// <param name="overwrite">
|
|
/// Whether to overwrite the specified path if a folder already exists.
|
|
/// </param>
|
|
/// <param name="duration">
|
|
/// Optional time limit for the recording.
|
|
/// </param>
|
|
/// <param name="state">
|
|
/// An arbitrary object that is available in <see cref="ActiveRecordingState"/> and <see cref="RecordingFinished"/>.
|
|
/// </param>
|
|
/// <returns>Returns true if the recording was successfully started.</returns>
|
|
bool TryStartRecording(
|
|
IWritableDirProvider directory,
|
|
string? name = null,
|
|
bool overwrite = false,
|
|
TimeSpan? duration = null,
|
|
object? state = null);
|
|
|
|
/// <summary>
|
|
/// Stops an ongoing replay recording.
|
|
/// </summary>
|
|
void StopRecording();
|
|
|
|
/// <summary>
|
|
/// Returns information about the currently ongoing replay recording.
|
|
/// </summary>
|
|
ReplayRecordingStats GetReplayStats();
|
|
|
|
/// <summary>
|
|
/// Returns a task that will wait for all the current writing tasks to finish.
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown if we are currently recording (<see cref="IsRecording"/> true).
|
|
/// </exception>
|
|
Task WaitWriteTasks();
|
|
|
|
/// <summary>
|
|
/// Returns true if there are any currently running write tasks.
|
|
/// </summary>
|
|
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>
|
|
/// <param name="Directory">The writable dir provider in which the replay is being recorded.</param>
|
|
/// <param name="Path">The path to the replay in <paramref name="Directory"/>.</param>
|
|
/// <param name="State">The state object passed to <see cref="IReplayRecordingManager.TryStartRecording"/>.</param>
|
|
public record ReplayRecordingFinished(IWritableDirProvider Directory, ResPath Path, object? State);
|
|
|
|
/// <summary>
|
|
/// Statistics for an active replay recording.
|
|
/// </summary>
|
|
/// <param name="Time">The simulation time the replay has been recording for.</param>
|
|
/// <param name="Ticks">The amount of simulation ticks the replay has recorded.</param>
|
|
/// <param name="Size">The total compressed size of the replay data blobs.</param>
|
|
/// <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"/>
|
|
[NotContentImplementable]
|
|
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>
|
|
/// Writes a yaml document into a file in the replay.
|
|
/// </summary>
|
|
/// <param name="path">The file path to write to.</param>
|
|
/// <param name="yaml">The yaml document to write to the file.</param>
|
|
/// <param name="compressionLevel">How much to compress the file.</param>
|
|
void WriteYaml(
|
|
ResPath path,
|
|
YamlDocument yaml,
|
|
CompressionLevel compressionLevel = CompressionLevel.Optimal)
|
|
{
|
|
var memStream = new MemoryStream();
|
|
using var writer = new StreamWriter(memStream);
|
|
var yamlStream = new YamlStream {yaml};
|
|
yamlStream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
|
writer.Flush();
|
|
WriteBytes(path, memStream.AsMemory(), compressionLevel);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Engine-internal functions for <see cref="IReplayRecordingManager"/>.
|
|
/// </summary>
|
|
internal interface IReplayRecordingManagerInternal : IReplayRecordingManager
|
|
{
|
|
/// <summary>
|
|
/// Initializes the replay manager.
|
|
/// </summary>
|
|
void Initialize();
|
|
|
|
/// <summary>
|
|
/// Shut down any active replay recording, at engine shutdown.
|
|
/// </summary>
|
|
void Shutdown();
|
|
}
|