1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2025-07-31 02:59:01 +02:00

Migrate from MessagePack to MemoryPack for RPC serialization

This commit is contained in:
chylex 2022-10-18 22:47:43 +02:00
parent ff5d31bf05
commit 24e08f1943
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
39 changed files with 305 additions and 301 deletions

View File

@ -33,7 +33,7 @@ sealed class Instance : IDisposable {
private readonly LaunchServices launchServices;
private readonly PortManager portManager;
private InstanceStatus currentStatus;
private IInstanceStatus currentStatus;
private IInstanceState currentState;
private readonly SemaphoreSlim stateTransitioningActionSemaphore = new (1, 1);
@ -47,7 +47,7 @@ sealed class Instance : IDisposable {
this.launchServices = launchServices;
this.portManager = portManager;
this.currentState = new InstanceNotRunningState();
this.currentStatus = InstanceStatus.IsNotRunning;
this.currentStatus = InstanceStatus.NotRunning;
}
private async Task ReportLastStatus() {
@ -137,7 +137,7 @@ sealed class Instance : IDisposable {
public override ILogger Logger => instance.logger;
public override string ShortName => instance.shortName;
public override void ReportStatus(InstanceStatus newStatus) {
public override void ReportStatus(IInstanceStatus newStatus) {
int myStatusUpdateCounter = Interlocked.Increment(ref statusUpdateCounter);
instance.launchServices.TaskManager.Run(async () => {

View File

@ -19,7 +19,7 @@ abstract class InstanceContext {
Launcher = launcher;
}
public abstract void ReportStatus(InstanceStatus newStatus);
public abstract void ReportStatus(IInstanceStatus newStatus);
public abstract void TransitionState(Func<IInstanceState> newState);
public void TransitionState(IInstanceState newState) {

View File

@ -33,7 +33,7 @@ sealed class InstanceLaunchingState : IInstanceState, IDisposable {
if (lastDownloadProgress != progress) {
lastDownloadProgress = progress;
context.ReportStatus(new InstanceStatus.Downloading(progress));
context.ReportStatus(InstanceStatus.Downloading(progress));
}
}
@ -52,7 +52,7 @@ sealed class InstanceLaunchingState : IInstanceState, IDisposable {
throw new LaunchFailureException(InstanceLaunchFailReason.UnknownError, "Session failed to launch.");
}
context.ReportStatus(InstanceStatus.IsLaunching);
context.ReportStatus(InstanceStatus.Launching);
return launchSuccess.Session;
}
@ -60,7 +60,7 @@ sealed class InstanceLaunchingState : IInstanceState, IDisposable {
context.TransitionState(() => {
if (cancellationTokenSource.IsCancellationRequested) {
context.PortManager.Release(context.Configuration);
context.ReportStatus(InstanceStatus.IsNotRunning);
context.ReportStatus(InstanceStatus.NotRunning);
return new InstanceNotRunningState();
}
else {
@ -72,10 +72,10 @@ sealed class InstanceLaunchingState : IInstanceState, IDisposable {
private void OnLaunchFailure(Task task) {
if (task.Exception is { InnerException: LaunchFailureException e }) {
context.Logger.Error(e.LogMessage);
context.ReportStatus(new InstanceStatus.Failed(e.Reason));
context.ReportStatus(InstanceStatus.Failed(e.Reason));
}
else {
context.ReportStatus(new InstanceStatus.Failed(InstanceLaunchFailReason.UnknownError));
context.ReportStatus(InstanceStatus.Failed(InstanceLaunchFailReason.UnknownError));
}
context.PortManager.Release(context.Configuration);

View File

@ -17,11 +17,11 @@ sealed class InstanceNotRunningState : IInstanceState {
};
if (failReason != null) {
context.ReportStatus(new InstanceStatus.Failed(failReason.Value));
context.ReportStatus(InstanceStatus.Failed(failReason.Value));
return (this, LaunchInstanceResult.LaunchInitiated);
}
context.ReportStatus(InstanceStatus.IsLaunching);
context.ReportStatus(InstanceStatus.Launching);
return (new InstanceLaunchingState(context), LaunchInstanceResult.LaunchInitiated);
}

View File

@ -30,12 +30,12 @@ sealed class InstanceRunningState : IInstanceState {
if (session.HasEnded) {
if (sessionObjects.Dispose()) {
context.Logger.Warning("Session ended immediately after it was started.");
context.ReportStatus(new InstanceStatus.Failed(InstanceLaunchFailReason.UnknownError));
context.ReportStatus(InstanceStatus.Failed(InstanceLaunchFailReason.UnknownError));
context.LaunchServices.TaskManager.Run(() => context.TransitionState(new InstanceNotRunningState()));
}
}
else {
context.ReportStatus(InstanceStatus.IsRunning);
context.ReportStatus(InstanceStatus.Running);
context.Logger.Information("Session started.");
}
}
@ -52,12 +52,12 @@ sealed class InstanceRunningState : IInstanceState {
if (isStopping) {
context.Logger.Information("Session ended.");
context.ReportStatus(InstanceStatus.IsNotRunning);
context.ReportStatus(InstanceStatus.NotRunning);
context.TransitionState(new InstanceNotRunningState());
}
else {
context.Logger.Information("Session ended unexpectedly, restarting...");
context.ReportStatus(InstanceStatus.IsRestarting);
context.ReportStatus(InstanceStatus.Restarting);
context.TransitionState(new InstanceLaunchingState(context));
}
}

View File

@ -19,7 +19,7 @@ sealed class InstanceStoppingState : IInstanceState, IDisposable {
public void Initialize() {
context.Logger.Information("Session stopping.");
context.ReportStatus(InstanceStatus.IsStopping);
context.ReportStatus(InstanceStatus.Stopping);
context.LaunchServices.TaskManager.Run(DoStop);
}
@ -32,7 +32,7 @@ sealed class InstanceStoppingState : IInstanceState, IDisposable {
await DoWaitForSessionToEnd();
} finally {
context.Logger.Information("Session stopped.");
context.ReportStatus(InstanceStatus.IsNotRunning);
context.ReportStatus(InstanceStatus.NotRunning);
context.TransitionState(new InstanceNotRunningState());
}
}

View File

@ -1,16 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Cryptography;
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Data.Agent;
[MessagePackObject]
[MemoryPackable]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public sealed class AgentAuthToken {
public sealed partial class AgentAuthToken {
internal const int Length = 12;
[Key(0)]
public byte[] Bytes { get; }
[MemoryPackOrder(0)]
[MemoryPackInclude]
private readonly byte[] bytes;
public AgentAuthToken(byte[]? bytes) {
if (bytes == null) {
@ -21,15 +22,15 @@ public sealed class AgentAuthToken {
throw new ArgumentOutOfRangeException(nameof(bytes), "Invalid token length: " + bytes.Length + ". Token length must be exactly " + Length + " bytes.");
}
this.Bytes = bytes;
this.bytes = bytes;
}
public bool FixedTimeEquals(AgentAuthToken providedAuthToken) {
return CryptographicOperations.FixedTimeEquals(Bytes, providedAuthToken.Bytes);
return CryptographicOperations.FixedTimeEquals(bytes, providedAuthToken.bytes);
}
internal void WriteTo(Span<byte> span) {
Bytes.CopyTo(span);
bytes.CopyTo(span);
}
public static AgentAuthToken Generate() {

View File

@ -1,14 +1,14 @@
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Data.Agent;
[MessagePackObject]
public sealed record AgentInfo(
[property: Key(0)] Guid Guid,
[property: Key(1)] string Name,
[property: Key(2)] ushort Version,
[property: Key(3)] ushort MaxInstances,
[property: Key(4)] RamAllocationUnits MaxMemory,
[property: Key(5)] AllowedPorts AllowedServerPorts,
[property: Key(6)] AllowedPorts AllowedRconPorts
[MemoryPackable]
public sealed partial record AgentInfo(
[property: MemoryPackOrder(0)] Guid Guid,
[property: MemoryPackOrder(1)] string Name,
[property: MemoryPackOrder(2)] ushort Version,
[property: MemoryPackOrder(3)] ushort MaxInstances,
[property: MemoryPackOrder(4)] RamAllocationUnits MaxMemory,
[property: MemoryPackOrder(5)] AllowedPorts AllowedServerPorts,
[property: MemoryPackOrder(6)] AllowedPorts AllowedRconPorts
);

View File

@ -1,29 +1,28 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Data;
[MessagePackObject]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public sealed class AllowedPorts {
[Key(0)]
public ImmutableArray<PortRange> AllDefinitions { get; }
[MemoryPackable]
public sealed partial class AllowedPorts {
[MemoryPackOrder(0)]
[MemoryPackInclude]
private readonly ImmutableArray<PortRange> allDefinitions;
public AllowedPorts(ImmutableArray<PortRange> allDefinitions) {
private AllowedPorts(ImmutableArray<PortRange> allDefinitions) {
// TODO normalize and deduplicate ranges
this.AllDefinitions = allDefinitions.Sort(static (def1, def2) => def1.FirstPort - def2.FirstPort);
this.allDefinitions = allDefinitions.Sort(static (def1, def2) => def1.FirstPort - def2.FirstPort);
}
public bool Contains(ushort port) {
return AllDefinitions.Any(definition => definition.Contains(port));
return allDefinitions.Any(definition => definition.Contains(port));
}
public override string ToString() {
var builder = new StringBuilder();
foreach (var definition in AllDefinitions) {
foreach (var definition in allDefinitions) {
definition.ToString(builder);
builder.Append(',');
}
@ -35,53 +34,7 @@ public sealed class AllowedPorts {
return builder.ToString();
}
[MessagePackObject]
public readonly record struct PortRange(
[property: Key(0)] ushort FirstPort,
[property: Key(1)] ushort LastPort
) {
private PortRange(ushort port) : this(port, port) {}
internal bool Contains(ushort port) {
return port >= FirstPort && port <= LastPort;
}
internal void ToString(StringBuilder builder) {
builder.Append(FirstPort);
if (LastPort != FirstPort) {
builder.Append('-');
builder.Append(LastPort);
}
}
internal static PortRange Parse(ReadOnlySpan<char> definition) {
int separatorIndex = definition.IndexOf('-');
if (separatorIndex == -1) {
var port = ParsePort(definition.Trim());
return new PortRange(port);
}
var firstPort = ParsePort(definition[..separatorIndex].Trim());
var lastPort = ParsePort(definition[(separatorIndex + 1)..].Trim());
if (lastPort < firstPort) {
throw new FormatException("Invalid port range '" + firstPort + "-" + lastPort + "'.");
}
else {
return new PortRange(firstPort, lastPort);
}
}
private static ushort ParsePort(ReadOnlySpan<char> port) {
try {
return ushort.Parse(port);
} catch (Exception) {
throw new FormatException("Invalid port '" + port.ToString() + "'.");
}
}
}
public static AllowedPorts FromString(ReadOnlySpan<char> definitions) {
private static AllowedPorts FromString(ReadOnlySpan<char> definitions) {
List<PortRange> parsedDefinitions = new ();
while (!definitions.IsEmpty) {

View File

@ -0,0 +1,67 @@
using MemoryPack;
namespace Phantom.Common.Data.Instance;
[MemoryPackable]
[MemoryPackUnion(0, typeof(InstanceIsOffline))]
[MemoryPackUnion(1, typeof(InstanceIsInvalid))]
[MemoryPackUnion(2, typeof(InstanceIsNotRunning))]
[MemoryPackUnion(3, typeof(InstanceIsDownloading))]
[MemoryPackUnion(4, typeof(InstanceIsLaunching))]
[MemoryPackUnion(5, typeof(InstanceIsRunning))]
[MemoryPackUnion(6, typeof(InstanceIsRestarting))]
[MemoryPackUnion(7, typeof(InstanceIsStopping))]
[MemoryPackUnion(8, typeof(InstanceIsFailed))]
public partial interface IInstanceStatus {}
[MemoryPackable]
public sealed partial record InstanceIsOffline : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsInvalid([property: MemoryPackOrder(0)] string Reason) : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsNotRunning : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsDownloading([property: MemoryPackOrder(0)] byte Progress) : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsLaunching : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsRunning : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsRestarting : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsStopping : IInstanceStatus;
[MemoryPackable]
public sealed partial record InstanceIsFailed([property: MemoryPackOrder(0)] InstanceLaunchFailReason Reason) : IInstanceStatus;
public static class InstanceStatus {
public static readonly IInstanceStatus Offline = new InstanceIsOffline();
public static readonly IInstanceStatus NotRunning = new InstanceIsNotRunning();
public static readonly IInstanceStatus Launching = new InstanceIsLaunching();
public static readonly IInstanceStatus Running = new InstanceIsRunning();
public static readonly IInstanceStatus Restarting = new InstanceIsRestarting();
public static readonly IInstanceStatus Stopping = new InstanceIsStopping();
public static IInstanceStatus Invalid(string reason) => new InstanceIsInvalid(reason);
public static IInstanceStatus Downloading(byte progress) => new InstanceIsDownloading(progress);
public static IInstanceStatus Failed(InstanceLaunchFailReason reason) => new InstanceIsFailed(reason);
public static bool CanLaunch(this IInstanceStatus status) {
return status is InstanceIsNotRunning or InstanceIsFailed;
}
public static bool CanStop(this IInstanceStatus status) {
return status is InstanceIsDownloading or InstanceIsLaunching or InstanceIsRunning;
}
public static bool CanSendCommand(this IInstanceStatus status) {
return status is InstanceIsRunning;
}
}

View File

@ -1,20 +1,20 @@
using System.Collections.Immutable;
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Minecraft;
namespace Phantom.Common.Data.Instance;
[MessagePackObject]
public sealed record InstanceConfiguration(
[property: Key(0)] Guid AgentGuid,
[property: Key(1)] Guid InstanceGuid,
[property: Key(2)] string InstanceName,
[property: Key(3)] ushort ServerPort,
[property: Key(4)] ushort RconPort,
[property: Key(5)] string MinecraftVersion,
[property: Key(6)] MinecraftServerKind MinecraftServerKind,
[property: Key(7)] RamAllocationUnits MemoryAllocation,
[property: Key(8)] Guid JavaRuntimeGuid,
[property: Key(9)] ImmutableArray<string> JvmArguments,
[property: Key(10)] bool LaunchAutomatically
[MemoryPackable]
public sealed partial record InstanceConfiguration(
[property: MemoryPackOrder(0)] Guid AgentGuid,
[property: MemoryPackOrder(1)] Guid InstanceGuid,
[property: MemoryPackOrder(2)] string InstanceName,
[property: MemoryPackOrder(3)] ushort ServerPort,
[property: MemoryPackOrder(4)] ushort RconPort,
[property: MemoryPackOrder(5)] string MinecraftVersion,
[property: MemoryPackOrder(6)] MinecraftServerKind MinecraftServerKind,
[property: MemoryPackOrder(7)] RamAllocationUnits MemoryAllocation,
[property: MemoryPackOrder(8)] Guid JavaRuntimeGuid,
[property: MemoryPackOrder(9)] ImmutableArray<string> JvmArguments,
[property: MemoryPackOrder(10)] bool LaunchAutomatically
);

View File

@ -1,68 +0,0 @@
using MessagePack;
namespace Phantom.Common.Data.Instance;
[Union(0, typeof(Offline))]
[Union(1, typeof(Invalid))]
[Union(2, typeof(NotRunning))]
[Union(3, typeof(Downloading))]
[Union(4, typeof(Launching))]
[Union(5, typeof(Running))]
[Union(6, typeof(Restarting))]
[Union(7, typeof(Stopping))]
[Union(8, typeof(Failed))]
public abstract record InstanceStatus {
public static readonly InstanceStatus IsOffline = new Offline();
public static readonly InstanceStatus IsNotRunning = new NotRunning();
public static readonly InstanceStatus IsLaunching = new Launching();
public static readonly InstanceStatus IsRunning = new Running();
public static readonly InstanceStatus IsRestarting = new Restarting();
public static readonly InstanceStatus IsStopping = new Stopping();
[MessagePackObject]
public sealed record Offline : InstanceStatus;
[MessagePackObject]
public sealed record Invalid(
[property: Key(0)] string Reason
) : InstanceStatus;
[MessagePackObject]
public sealed record NotRunning : InstanceStatus;
[MessagePackObject]
public sealed record Downloading(
[property: Key(0)] byte Progress
) : InstanceStatus;
[MessagePackObject]
public sealed record Launching : InstanceStatus;
[MessagePackObject]
public sealed record Running : InstanceStatus;
[MessagePackObject]
public sealed record Restarting : InstanceStatus;
[MessagePackObject]
public sealed record Stopping : InstanceStatus;
[MessagePackObject]
public sealed record Failed(
[property: Key(0)] InstanceLaunchFailReason Reason
) : InstanceStatus;
}
public static class InstanceStatusExtensions {
public static bool CanLaunch(this InstanceStatus status) {
return status is InstanceStatus.NotRunning or InstanceStatus.Failed;
}
public static bool CanStop(this InstanceStatus status) {
return status is InstanceStatus.Downloading or InstanceStatus.Launching or InstanceStatus.Running;
}
public static bool CanSendCommand(this InstanceStatus status) {
return status is InstanceStatus.Running;
}
}

View File

@ -1,13 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Data.Java;
[MessagePackObject]
public sealed record JavaRuntime(
[property: Key(0)] string MainVersion,
[property: Key(1)] string FullVersion,
[property: Key(2)] string DisplayName
[MemoryPackable]
public sealed partial record JavaRuntime(
[property: MemoryPackOrder(0)] string MainVersion,
[property: MemoryPackOrder(1)] string FullVersion,
[property: MemoryPackOrder(2)] string DisplayName
) : IComparable<JavaRuntime> {
public int CompareTo(JavaRuntime? other) {
if (ReferenceEquals(this, other)) {

View File

@ -1,9 +1,9 @@
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Data.Java;
[MessagePackObject]
public sealed record TaggedJavaRuntime(
[property: Key(0)] Guid Guid,
[property: Key(1)] JavaRuntime Runtime
[MemoryPackable]
public sealed partial record TaggedJavaRuntime(
[property: MemoryPackOrder(0)] Guid Guid,
[property: MemoryPackOrder(1)] JavaRuntime Runtime
);

View File

@ -1,10 +1,10 @@
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Data.Minecraft;
[MessagePackObject]
public readonly record struct MinecraftStopStrategy(
[property: Key(0)] ushort Seconds
[MemoryPackable]
public readonly partial record struct MinecraftStopStrategy(
[property: MemoryPackOrder(0)] ushort Seconds
) {
public static MinecraftStopStrategy Instant => new (0);
}

View File

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MessagePack.Annotations" />
<PackageReference Include="MemoryPack" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,48 @@
using System.Text;
using MemoryPack;
namespace Phantom.Common.Data;
[MemoryPackable]
public readonly partial record struct PortRange(
[property: MemoryPackOrder(0)] ushort FirstPort,
[property: MemoryPackOrder(1)] ushort LastPort
) {
internal bool Contains(ushort port) {
return port >= FirstPort && port <= LastPort;
}
internal void ToString(StringBuilder builder) {
builder.Append(FirstPort);
if (LastPort != FirstPort) {
builder.Append('-');
builder.Append(LastPort);
}
}
internal static PortRange Parse(ReadOnlySpan<char> definition) {
int separatorIndex = definition.IndexOf('-');
if (separatorIndex == -1) {
var port = ParsePort(definition.Trim());
return new PortRange(port, port);
}
var firstPort = ParsePort(definition[..separatorIndex].Trim());
var lastPort = ParsePort(definition[(separatorIndex + 1)..].Trim());
if (lastPort < firstPort) {
throw new FormatException("Invalid port range '" + firstPort + "-" + lastPort + "'.");
}
else {
return new PortRange(firstPort, lastPort);
}
}
private static ushort ParsePort(ReadOnlySpan<char> port) {
try {
return ushort.Parse(port);
} catch (Exception) {
throw new FormatException("Invalid port '" + port.ToString() + "'.");
}
}
}

View File

@ -1,17 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Data;
/// <summary>
/// Represents a number of RAM allocation units, using the conversion factor of 256 MB per unit. Supports allocations up to 16 TB minus 256 MB (65535 units).
/// </summary>
[MessagePackObject]
[MemoryPackable]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public readonly record struct RamAllocationUnits(
[property: Key(0)] ushort RawValue
public readonly partial record struct RamAllocationUnits(
[property: MemoryPackOrder(0)] ushort RawValue
) : IComparable<RamAllocationUnits> {
[IgnoreMember]
[MemoryPackIgnore]
public uint InMegabytes => (uint) RawValue * MegabytesPerUnit;
public int CompareTo(RamAllocationUnits other) {

View File

@ -1,12 +1,12 @@
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Instance;
namespace Phantom.Common.Messages.ToAgent;
[MessagePackObject]
public sealed record ConfigureInstanceMessage(
[property: Key(0)] uint SequenceId,
[property: Key(1)] InstanceConfiguration Configuration
[MemoryPackable]
public sealed partial record ConfigureInstanceMessage(
[property: MemoryPackOrder(0)] uint SequenceId,
[property: MemoryPackOrder(1)] InstanceConfiguration Configuration
) : IMessageToAgent, IMessageWithReply {
public Task Accept(IMessageToAgentListener listener) {
return listener.HandleConfigureInstance(this);

View File

@ -1,11 +1,11 @@
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Messages.ToAgent;
[MessagePackObject]
public sealed record LaunchInstanceMessage(
[property: Key(0)] uint SequenceId,
[property: Key(1)] Guid InstanceGuid
[MemoryPackable]
public sealed partial record LaunchInstanceMessage(
[property: MemoryPackOrder(0)] uint SequenceId,
[property: MemoryPackOrder(1)] Guid InstanceGuid
) : IMessageToAgent, IMessageWithReply {
public Task Accept(IMessageToAgentListener listener) {
return listener.HandleLaunchInstance(this);

View File

@ -1,11 +1,11 @@
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Replies;
namespace Phantom.Common.Messages.ToAgent;
[MessagePackObject]
public sealed record RegisterAgentFailureMessage(
[property: Key(0)] RegisterAgentFailure FailureKind
[MemoryPackable]
public sealed partial record RegisterAgentFailureMessage(
[property: MemoryPackOrder(0)] RegisterAgentFailure FailureKind
) : IMessageToAgent {
public Task Accept(IMessageToAgentListener listener) {
return listener.HandleRegisterAgentFailureResult(this);

View File

@ -1,12 +1,12 @@
using System.Collections.Immutable;
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Instance;
namespace Phantom.Common.Messages.ToAgent;
[MessagePackObject]
public sealed record RegisterAgentSuccessMessage(
[property: Key(0)] ImmutableArray<InstanceConfiguration> InitialInstances
[MemoryPackable]
public sealed partial record RegisterAgentSuccessMessage(
[property: MemoryPackOrder(0)] ImmutableArray<InstanceConfiguration> InitialInstances
) : IMessageToAgent {
public Task Accept(IMessageToAgentListener listener) {
return listener.HandleRegisterAgentSuccessResult(this);

View File

@ -1,12 +1,12 @@
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Messages.ToAgent;
[MessagePackObject]
public sealed record SendCommandToInstanceMessage(
[property: Key(0)] uint SequenceId,
[property: Key(1)] Guid InstanceGuid,
[property: Key(2)] string Command
[MemoryPackable]
public sealed partial record SendCommandToInstanceMessage(
[property: MemoryPackOrder(0)] uint SequenceId,
[property: MemoryPackOrder(1)] Guid InstanceGuid,
[property: MemoryPackOrder(2)] string Command
) : IMessageToAgent, IMessageWithReply {
public Task Accept(IMessageToAgentListener listener) {
return listener.HandleSendCommandToInstance(this);

View File

@ -1,13 +1,13 @@
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Minecraft;
namespace Phantom.Common.Messages.ToAgent;
[MessagePackObject]
public sealed record StopInstanceMessage(
[property: Key(0)] uint SequenceId,
[property: Key(1)] Guid InstanceGuid,
[property: Key(2)] MinecraftStopStrategy StopStrategy
[MemoryPackable]
public sealed partial record StopInstanceMessage(
[property: MemoryPackOrder(0)] uint SequenceId,
[property: MemoryPackOrder(1)] Guid InstanceGuid,
[property: MemoryPackOrder(2)] MinecraftStopStrategy StopStrategy
) : IMessageToAgent, IMessageWithReply {
public Task Accept(IMessageToAgentListener listener) {
return listener.HandleStopInstance(this);

View File

@ -1,12 +1,12 @@
using System.Collections.Immutable;
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Java;
namespace Phantom.Common.Messages.ToServer;
[MessagePackObject]
public sealed record AdvertiseJavaRuntimesMessage(
[property: Key(0)] ImmutableArray<TaggedJavaRuntime> Runtimes
[MemoryPackable]
public sealed partial record AdvertiseJavaRuntimesMessage(
[property: MemoryPackOrder(0)] ImmutableArray<TaggedJavaRuntime> Runtimes
) : IMessageToServer {
public Task Accept(IMessageToServerListener listener) {
return listener.HandleAdvertiseJavaRuntimes(this);

View File

@ -1,9 +1,9 @@
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Messages.ToServer;
[MessagePackObject]
public sealed record AgentIsAliveMessage : IMessageToServer {
[MemoryPackable]
public sealed partial record AgentIsAliveMessage : IMessageToServer {
public Task Accept(IMessageToServerListener listener) {
return listener.HandleAgentIsAlive(this);
}

View File

@ -1,12 +1,12 @@
using System.Collections.Immutable;
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Messages.ToServer;
[MessagePackObject]
public sealed record InstanceOutputMessage(
[property: Key(0)] Guid InstanceGuid,
[property: Key(1)] ImmutableArray<string> Lines
[MemoryPackable]
public sealed partial record InstanceOutputMessage(
[property: MemoryPackOrder(0)] Guid InstanceGuid,
[property: MemoryPackOrder(1)] ImmutableArray<string> Lines
) : IMessageToServer {
public Task Accept(IMessageToServerListener listener) {
return listener.HandleInstanceOutput(this);

View File

@ -1,12 +1,12 @@
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Agent;
namespace Phantom.Common.Messages.ToServer;
[MessagePackObject]
public sealed record RegisterAgentMessage(
[property: Key(0)] AgentAuthToken AuthToken,
[property: Key(1)] AgentInfo AgentInfo
[MemoryPackable]
public sealed partial record RegisterAgentMessage(
[property: MemoryPackOrder(0)] AgentAuthToken AuthToken,
[property: MemoryPackOrder(1)] AgentInfo AgentInfo
) : IMessageToServer {
public Task Accept(IMessageToServerListener listener) {
return listener.HandleRegisterAgent(this);

View File

@ -1,12 +1,12 @@
using MessagePack;
using MemoryPack;
using Phantom.Common.Data.Instance;
namespace Phantom.Common.Messages.ToServer;
[MessagePackObject]
public sealed record ReportInstanceStatusMessage(
[property: Key(0)] Guid InstanceGuid,
[property: Key(1)] InstanceStatus InstanceStatus
[MemoryPackable]
public sealed partial record ReportInstanceStatusMessage(
[property: MemoryPackOrder(0)] Guid InstanceGuid,
[property: MemoryPackOrder(1)] IInstanceStatus InstanceStatus
) : IMessageToServer {
public Task Accept(IMessageToServerListener listener) {
return listener.HandleReportInstanceStatus(this);

View File

@ -1,12 +1,12 @@
using System.Runtime.CompilerServices;
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Messages.ToServer;
[MessagePackObject]
public sealed record SimpleReplyMessage(
[property: Key(0)] uint SequenceId,
[property: Key(1)] int EnumValue
[MemoryPackable]
public sealed partial record SimpleReplyMessage(
[property: MemoryPackOrder(0)] uint SequenceId,
[property: MemoryPackOrder(1)] int EnumValue
) : IMessageToServer {
public static SimpleReplyMessage FromEnum<TEnum>(uint sequenceId, TEnum enumValue) where TEnum : Enum {
if (Unsafe.SizeOf<TEnum>() != Unsafe.SizeOf<int>()) {

View File

@ -1,10 +1,10 @@
using MessagePack;
using MemoryPack;
namespace Phantom.Common.Messages.ToServer;
[MessagePackObject]
public sealed record UnregisterAgentMessage(
[property: Key(0)] Guid AgentGuid
[MemoryPackable]
public sealed partial record UnregisterAgentMessage(
[property: MemoryPackOrder(0)] Guid AgentGuid
) : IMessageToServer {
public Task Accept(IMessageToServerListener listener) {
return listener.HandleUnregisterAgent(this);

View File

@ -12,9 +12,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Update="MessagePack" Version="2.4.35" />
<PackageReference Update="MessagePack.Annotations" Version="2.4.35" />
<PackageReference Update="NetMQ" Version="4.0.1.10" />
<PackageReference Update="MemoryPack" Version="1.4.1" />
<PackageReference Update="NetMQ" Version="4.0.1.10" />
</ItemGroup>
<ItemGroup>

View File

@ -4,7 +4,7 @@ namespace Phantom.Server.Services.Instances;
public sealed record Instance(
InstanceConfiguration Configuration,
InstanceStatus Status
IInstanceStatus Status
) {
internal Instance(InstanceConfiguration configuration) : this(configuration, InstanceStatus.IsOffline) {}
internal Instance(InstanceConfiguration configuration) : this(configuration, InstanceStatus.Offline) {}
}

View File

@ -113,11 +113,11 @@ public sealed class InstanceManager {
return instances.ByGuid.TryGetValue(instanceGuid, out var instance) ? instance : null;
}
internal void SetInstanceState(Guid instanceGuid, InstanceStatus instanceStatus) {
internal void SetInstanceState(Guid instanceGuid, IInstanceStatus instanceStatus) {
instances.ByGuid.TryReplace(instanceGuid, instance => instance with { Status = instanceStatus });
}
internal void SetInstanceStatesForAgent(Guid agentGuid, InstanceStatus instanceStatus) {
internal void SetInstanceStatesForAgent(Guid agentGuid, IInstanceStatus instanceStatus) {
instances.ByGuid.ReplaceAllIf(instance => instance with { Status = instanceStatus }, instance => instance.Configuration.AgentGuid == agentGuid);
}

View File

@ -50,7 +50,7 @@ public sealed class MessageToServerListener : IMessageToServerListener {
IsDisposed = true;
if (agentManager.UnregisterAgent(message.AgentGuid, connection)) {
instanceManager.SetInstanceStatesForAgent(message.AgentGuid, InstanceStatus.IsOffline);
instanceManager.SetInstanceStatesForAgent(message.AgentGuid, InstanceStatus.Offline);
}
return Task.CompletedTask;

View File

@ -1,43 +1,43 @@
@using Phantom.Common.Data.Instance
@switch (Status) {
case InstanceStatus.Offline:
case InstanceIsOffline:
<text>Offline</text>
break;
case InstanceStatus.Invalid invalid:
case InstanceIsInvalid invalid:
<text>Invalid <sup title="@invalid.Reason">[?]</sup></text>
break;
case InstanceStatus.NotRunning:
case InstanceIsNotRunning:
<text>Not Running</text>
break;
case InstanceStatus.Downloading downloading:
case InstanceIsDownloading downloading:
<ProgressBar Value="@downloading.Progress" Maximum="100">
Downloading Server (@downloading.Progress%)
</ProgressBar>
break;
case InstanceStatus.Launching:
case InstanceIsLaunching:
<div class="spinner-border spinner-border-sm" role="status"></div>
<text>&nbsp;Launching</text>
break;
case InstanceStatus.Running:
case InstanceIsRunning:
<text>Running</text>
break;
case InstanceStatus.Restarting:
case InstanceIsRestarting:
<div class="spinner-border spinner-border-sm" role="status"></div>
<text>&nbsp;Restarting</text>
break;
case InstanceStatus.Stopping:
case InstanceIsStopping:
<div class="spinner-border spinner-border-sm" role="status"></div>
<text>&nbsp;Stopping</text>
break;
case InstanceStatus.Failed failed:
case InstanceIsFailed failed:
<text>Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></text>
break;
}
@ -45,6 +45,6 @@
@code {
[Parameter]
public InstanceStatus? Status { get; set; }
public IInstanceStatus? Status { get; set; }
}

View File

@ -1,14 +1,18 @@
using System.Diagnostics.CodeAnalysis;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using Phantom.Utils.Runtime;
using Serilog;
using Serilog.Events;
namespace Phantom.Utils.Rpc.Message;
public sealed class MessageRegistry<TListener, TMessageBase> where TMessageBase : class, IMessage<TListener> {
private const int DefaultBufferSize = 512;
private readonly ILogger logger;
private readonly Dictionary<Type, ushort> typeToCodeMapping = new ();
private readonly Dictionary<ushort, Type> codeToTypeMapping = new ();
private readonly Dictionary<ushort, Func<ReadOnlyMemory<byte>, CancellationToken, TMessageBase>> codeToDeserializerMapping = new ();
private readonly Dictionary<ushort, Func<ReadOnlyMemory<byte>, TMessageBase>> codeToDeserializerMapping = new ();
public MessageRegistry(ILogger logger) {
this.logger = logger;
@ -32,20 +36,25 @@ public sealed class MessageRegistry<TListener, TMessageBase> where TMessageBase
}
}
public ReadOnlySpan<byte> Write<TMessage>(TMessage message, CancellationToken cancellationToken = default) where TMessage : TMessageBase {
public ReadOnlySpan<byte> Write<TMessage>(TMessage message) where TMessage : TMessageBase {
if (!typeToCodeMapping.TryGetValue(typeof(TMessage), out ushort code)) {
logger.Error("Unknown message type {Type}.", typeof(TMessage));
return default;
}
var stream = new MemoryStream();
var buffer = new ArrayBufferWriter<byte>(DefaultBufferSize);
try {
MessageSerializer.WriteCode(stream, code);
MessageSerializer.Serialize<TMessage, TListener>(stream, message, cancellationToken);
return new ReadOnlySpan<byte>(stream.GetBuffer(), 0, (int) stream.Length);
MessageSerializer.WriteCode(buffer, code);
MessageSerializer.Serialize<TMessage, TListener>(buffer, message);
if (buffer.WrittenCount > DefaultBufferSize && logger.IsEnabled(LogEventLevel.Verbose)) {
logger.Verbose("Serializing {Type} exceeded default buffer size: {WrittenSize} B > {DefaultBufferSize} B", typeof(TMessage).Name, buffer.WrittenCount, DefaultBufferSize);
}
return buffer.WrittenSpan;
} catch (Exception e) {
logger.Error(e, "Failed to serialize message {Type}.", typeof(TMessage));
logger.Error(e, "Failed to serialize message {Type}.", typeof(TMessage).Name);
return default;
}
}
@ -68,7 +77,7 @@ public sealed class MessageRegistry<TListener, TMessageBase> where TMessageBase
TMessageBase message;
try {
message = deserialize(memory, cancellationToken);
message = deserialize(memory);
} catch (Exception e) {
logger.Error(e, "Failed to deserialize message with code {Code}.", code);
return;
@ -78,7 +87,7 @@ public sealed class MessageRegistry<TListener, TMessageBase> where TMessageBase
try {
await message.Accept(listener);
} catch (Exception e) {
logger.Error(e, "Failed to handle message {Type}.", message.GetType());
logger.Error(e, "Failed to handle message {Type}.", message.GetType().Name);
}
}

View File

@ -1,29 +1,24 @@
using System.Buffers.Binary;
using MessagePack;
using MessagePack.Resolvers;
using System.Buffers;
using System.Buffers.Binary;
using MemoryPack;
namespace Phantom.Utils.Rpc.Message;
static class MessageSerializer {
private static readonly MessagePackSerializerOptions SerializerOptions =
MessagePackSerializerOptions
.Standard
.WithResolver(CompositeResolver.Create(NativeGuidResolver.Instance, StandardResolver.Instance))
.WithCompression(MessagePackCompression.None)
.WithSecurity(MessagePackSecurity.UntrustedData.WithMaximumObjectGraphDepth(10));
private static readonly MemoryPackSerializeOptions SerializerOptions = MemoryPackSerializeOptions.Utf8;
public static void Serialize<TMessage, TListener>(Stream stream, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage<TListener> {
MessagePackSerializer.Serialize(stream, message, SerializerOptions, cancellationToken);
public static void Serialize<TMessage, TListener>(IBufferWriter<byte> destination, TMessage message) where TMessage : IMessage<TListener> {
MemoryPackSerializer.Serialize(typeof(TMessage), destination, message, SerializerOptions);
}
public static Func<ReadOnlyMemory<byte>, CancellationToken, TMessageBase> Deserialize<TMessage, TMessageBase, TListener>() where TMessageBase : IMessage<TListener> where TMessage : TMessageBase {
return static (memory, cancellationToken) => MessagePackSerializer.Deserialize<TMessage>(memory, SerializerOptions, cancellationToken);
public static Func<ReadOnlyMemory<byte>, TMessageBase> Deserialize<TMessage, TMessageBase, TListener>() where TMessageBase : IMessage<TListener> where TMessage : TMessageBase {
return static memory => MemoryPackSerializer.Deserialize<TMessage>(memory.Span) ?? throw new NullReferenceException();
}
public static void WriteCode(Stream stream, ushort value) {
public static void WriteCode(IBufferWriter<byte> destination, ushort value) {
Span<byte> buffer = stackalloc byte[2];
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
stream.Write(buffer);
destination.Write(buffer);
}
public static ushort ReadCode(ref ReadOnlyMemory<byte> memory) {

View File

@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MessagePack" />
<PackageReference Include="MemoryPack" />
<PackageReference Include="NetMQ" />
<PackageReference Include="Serilog" />
</ItemGroup>