Files
UnrealEngineUWP/Engine/Source/Programs/Shared/EpicGames.Redis/RedisSerializer.cs
Ben Marsh a18929e82f Horde: Add timing stats to remote execution responses.
Two sets of data are returned - one detailing the timeline of the task on the server (being queued, through dispatched, to completed), and one detailing the timeline of the task on the remote agent (downloading data, executing the task, and uploading the result).

The standalone compute command implementation in Horde.Agent also logs its own timeline of events.

#preflight none

[CL 19675739 by Ben Marsh in ue5-main branch]
2022-04-07 15:34:25 -04:00

237 lines
6.9 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using EpicGames.Serialization;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
namespace EpicGames.Redis
{
/// <summary>
/// Attribute specifying the converter type to use for a class
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class RedisConverterAttribute : Attribute
{
/// <summary>
/// Type of the converter to use
/// </summary>
public Type ConverterType { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="converterType">The converter type</param>
public RedisConverterAttribute(Type converterType)
{
ConverterType = converterType;
}
}
/// <summary>
/// Converter to and from RedisValue types
/// </summary>
public interface IRedisConverter<T>
{
/// <summary>
/// Serailize an object to a RedisValue
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
RedisValue ToRedisValue(T value);
/// <summary>
/// Deserialize an object from a RedisValue
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
T FromRedisValue(RedisValue value);
}
/// <summary>
/// Redis serializer that uses compact binary to serialize objects
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class RedisCbConverter<T> : IRedisConverter<T>
{
/// <inheritdoc/>
public RedisValue ToRedisValue(T value)
{
return CbSerializer.Serialize(value).GetView();
}
/// <inheritdoc/>
public T FromRedisValue(RedisValue value)
{
return CbSerializer.Deserialize<T>(new CbField((byte[])value));
}
}
/// <summary>
/// Handles serialization of types to RedisValue instances
/// </summary>
public static class RedisSerializer
{
class RedisStringConverter<T> : IRedisConverter<T>
{
readonly TypeConverter _typeConverter;
public RedisStringConverter(TypeConverter typeConverter)
{
_typeConverter = typeConverter;
}
public RedisValue ToRedisValue(T value) => (string)_typeConverter.ConvertTo(value, typeof(string));
public T FromRedisValue(RedisValue value) => (T)_typeConverter.ConvertFrom((string)value);
}
class RedisNativeConverter<T> : IRedisConverter<T>
{
readonly Func<RedisValue, T> _fromRedisValueFunc;
readonly Func<T, RedisValue> _toRedisValueFunc;
public RedisNativeConverter(Func<RedisValue, T> fromRedisValueFunc, Func<T, RedisValue> toRedisValueFunc)
{
_fromRedisValueFunc = fromRedisValueFunc;
_toRedisValueFunc = toRedisValueFunc;
}
public T FromRedisValue(RedisValue value) => _fromRedisValueFunc(value);
public RedisValue ToRedisValue(T value) => _toRedisValueFunc(value);
}
static readonly Dictionary<Type, object> s_nativeConverters = CreateNativeConverterLookup();
static Dictionary<Type, object> CreateNativeConverterLookup()
{
KeyValuePair<Type, object>[] converters =
{
CreateNativeConverter(x => (bool)x, x => x),
CreateNativeConverter(x => (int)x, x => x),
CreateNativeConverter(x => (int?)x, x => x),
CreateNativeConverter(x => (uint)x, x => x),
CreateNativeConverter(x => (uint?)x, x => x),
CreateNativeConverter(x => (long)x, x => x),
CreateNativeConverter(x => (long?)x, x => x),
CreateNativeConverter(x => (ulong)x, x => x),
CreateNativeConverter(x => (ulong?)x, x => x),
CreateNativeConverter(x => (double)x, x => x),
CreateNativeConverter(x => (double?)x, x => x),
CreateNativeConverter(x => (ReadOnlyMemory<byte>)x, x => x),
CreateNativeConverter(x => (byte[])x, x => x),
CreateNativeConverter(x => (string)x, x => x),
};
return new Dictionary<Type, object>(converters);
}
static KeyValuePair<Type, object> CreateNativeConverter<T>(Func<RedisValue, T> fromRedisValueFunc, Func<T, RedisValue> toRedisValueFunc)
{
return new KeyValuePair<Type, object>(typeof(T), new RedisNativeConverter<T>(fromRedisValueFunc, toRedisValueFunc));
}
static readonly Dictionary<Type, Type> s_typeToConverterType = new Dictionary<Type, Type>();
/// <summary>
/// Register a custom converter for a particular type
/// </summary>
public static void RegisterConverter<T, TConverter>() where TConverter : IRedisConverter<T>
{
lock (s_typeToConverterType)
{
if (!s_typeToConverterType.TryGetValue(typeof(T), out Type? converterType) || converterType != typeof(TConverter))
{
s_typeToConverterType.Add(typeof(T), typeof(TConverter));
}
}
}
/// <summary>
/// Creates a converter for a given type
/// </summary>
static IRedisConverter<T> CreateConverter<T>()
{
Type type = typeof(T);
// Check for a registered converter type
lock (s_typeToConverterType)
{
if (s_typeToConverterType.TryGetValue(type, out Type? converterType))
{
return (IRedisConverter<T>)Activator.CreateInstance(converterType)!;
}
}
// Check for a custom converter
RedisConverterAttribute? attribute = type.GetCustomAttribute<RedisConverterAttribute>();
if (attribute != null)
{
Type converterType = attribute.ConverterType;
if (converterType.IsGenericTypeDefinition)
{
converterType = converterType.MakeGenericType(type);
}
return (IRedisConverter<T>)Activator.CreateInstance(converterType)!;
}
// Check for known basic types
object? nativeConverter;
if (s_nativeConverters.TryGetValue(typeof(T), out nativeConverter))
{
return (IRedisConverter<T>)nativeConverter;
}
// Check if there's a regular converter we can use to convert to/from a string
TypeConverter? converter = TypeDescriptor.GetConverter(type);
if (converter != null && converter.CanConvertFrom(typeof(string)) && converter.CanConvertTo(typeof(string)))
{
return new RedisStringConverter<T>(converter);
}
throw new Exception($"Unable to find Redis converter for {type.Name}");
}
/// <summary>
/// Static class for caching converter lookups
/// </summary>
/// <typeparam name="T"></typeparam>
class CachedConverter<T>
{
public static IRedisConverter<T> Converter = CreateConverter<T>();
}
/// <summary>
/// Gets the converter for a particular type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static IRedisConverter<T> GetConverter<T>()
{
return CachedConverter<T>.Converter;
}
/// <summary>
/// Serialize an object to a <see cref="RedisValue"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static RedisValue Serialize<T>(T value)
{
return CachedConverter<T>.Converter.ToRedisValue(value);
}
/// <summary>
/// Deserialize a <see cref="RedisValue"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static T Deserialize<T>(RedisValue value)
{
return CachedConverter<T>.Converter.FromRedisValue(value);
}
}
}