// Copyright Epic Games, Inc. All Rights Reserved.
using System.Buffers.Binary;
using System.Text.Json;
using System.Text.Json.Serialization;
using EpicGames.Core;
namespace HordeAgent
{
///
/// Constants
///
public static class AgentMessagePipe
{
///
/// Name of the pipe to communicate over
///
public const string PipeName = "Horde.Agent";
}
///
/// Message
///
public enum AgentMessageType
{
///
/// Response indicating that the sent message was invalid
///
InvalidResponse = 0,
///
/// Request the current agent status.
///
GetStatusRequest = 1,
///
/// Returns the current status, as a Json-encoded message
///
GetStatusResponse = 2,
///
/// Sets the paused state
///
SetEnabledRequest = 3,
///
/// Gets information about the server we're connected to.
///
GetSettingsRequest = 4,
///
/// Returns information about the server we're connected to.
///
GetSettingsResponse = 5,
///
/// Set agent settings request
///
SetSettingsRequest = 6,
///
/// Set agent settings response
///
SetSettingsResponse = 7,
}
///
/// Buffer used to read/write agent messages
///
public class AgentMessageBuffer
{
static readonly JsonSerializerOptions s_jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };
readonly ArrayMemoryWriter _writer = new ArrayMemoryWriter(256);
///
/// Type of the message currently in the buffer
///
public AgentMessageType Type { get; private set; }
///
/// Gets the full message data
///
public ReadOnlyMemory Data => _writer.WrittenMemory;
///
/// Constructor
///
public AgentMessageBuffer()
{
_writer.Advance(4);
}
///
/// Sets the current message to the given type, with a json serialized body
///
/// Type of the message
/// The message body
public void Set(AgentMessageType type, object? message = null)
{
_writer.Clear();
_writer.Advance(4);
if (message != null)
{
using Utf8JsonWriter writer = new Utf8JsonWriter(_writer);
JsonSerializer.Serialize(writer, message, s_jsonOptions);
}
Type = type;
BinaryPrimitives.WriteUInt32BigEndian(_writer.WrittenSpan, ((uint)type << 24) | (uint)(_writer.Length - 4));
}
///
/// Reads a message from the given stream
///
/// Stream to read from
/// Cancellation token for the operation
public async Task TryReadAsync(Stream stream, CancellationToken cancellationToken)
{
_writer.Clear();
Memory headerData = _writer.GetMemoryAndAdvance(4);
int readLength = await stream.ReadAsync(headerData, cancellationToken);
if (readLength == 0)
{
return false;
}
if (readLength < headerData.Length)
{
await stream.ReadFixedLengthBytesAsync(headerData.Slice(readLength), cancellationToken);
}
uint header = BinaryPrimitives.ReadUInt32BigEndian(headerData.Span);
Type = (AgentMessageType)(header >> 24);
int length = (int)(header & 0xffffff);
await stream.ReadFixedLengthBytesAsync(_writer.GetMemoryAndAdvance(length), cancellationToken);
return true;
}
///
/// Writes the current message to a stream
///
/// Stream to write to
/// Cancellation token for the operation
public async Task SendAsync(Stream stream, CancellationToken cancellationToken)
{
await stream.WriteAsync(_writer.WrittenMemory, cancellationToken);
}
///
/// Parse the message body as a particular type
///
public T Parse()
{
Utf8JsonReader reader = new Utf8JsonReader(_writer.WrittenSpan.Slice(4));
return JsonSerializer.Deserialize(ref reader, s_jsonOptions)!;
}
}
///
/// Settings for the agent
///
/// Url of the Horde server
public record AgentSettingsMessage(Uri? ServerUrl);
///
/// Sets the enabled state for the agent
///
/// Whether the agent is enabled or not
public record AgentEnabledMessage(bool IsEnabled);
///
/// Set settings request message
///
/// Maximum number of logical CPU cores workloads should use
/// CPU core multiplier applied to CPU core count setting
public record AgentSetSettingsRequest(int CpuCount, double CpuMultiplier);
///
/// Current status of the agent
///
/// Whether the agent is healthy
/// Number of leases currently being executed
/// Description of the state
public record class AgentStatusMessage(bool Healthy, int NumLeases, string Detail)
{
///
/// Static status object for starting an agent
///
public const string Starting = "Starting up...";
///
/// Agent is waiting to be enrolled with the server
///
public const string WaitingForEnrollment = "Waiting for enrollment...";
///
/// Agent is connecting to the server
///
public const string ConnectingToServer = "Connecting to server...";
}
}