// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Buffers.Binary;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using EpicGames.Core;
namespace EpicGames.Horde
{
///
/// Normalized string identifier for a resource
///
[JsonSchemaString]
[JsonConverter(typeof(BinaryIdJsonConverter))]
[TypeConverter(typeof(BinaryIdTypeConverter))]
public readonly struct BinaryId : IEquatable, IComparable
{
readonly int _a;
readonly int _b;
readonly int _c;
///
/// Number of bytes in the identifier
///
public const int NumBytes = 12;
///
/// Number of characters when formatted as a string
///
public const int NumChars = NumBytes * 2;
///
/// Constructor
///
/// Bytes to parse
public BinaryId(ReadOnlySpan bytes)
{
_a = BinaryPrimitives.ReadInt32LittleEndian(bytes);
_b = BinaryPrimitives.ReadInt32LittleEndian(bytes[4..]);
_c = BinaryPrimitives.ReadInt32LittleEndian(bytes[8..]);
}
///
/// Parse a binary id from a string
///
public static BinaryId Parse(string text)
{
BinaryId id;
if (!TryParse(text, out id))
{
throw new FormatException($"Invalid BinaryId: {text}");
}
return id;
}
///
/// Parse a binary id from a string
///
public static BinaryId Parse(Utf8String text) => Parse(text.Span);
///
/// Parse a binary id from a string
///
public static BinaryId Parse(ReadOnlySpan text)
{
BinaryId id;
if (!TryParse(text, out id))
{
throw new FormatException($"Invalid BinaryId: {Encoding.UTF8.GetString(text)}");
}
return id;
}
///
/// Attempt to parse a binary id from a string
///
public static bool TryParse(string text, out BinaryId result)
{
Span bytes = stackalloc byte[NumBytes];
if (StringUtils.TryParseHexString(text, bytes))
{
result = new BinaryId(bytes);
return true;
}
else
{
result = default;
return false;
}
}
///
/// Attempt to parse a binary id from a string
///
public static bool TryParse(Utf8String text, out BinaryId result) => TryParse(text.Span, out result);
///
/// Attempt to parse a binary id from a string
///
public static bool TryParse(ReadOnlySpan text, out BinaryId result)
{
Span bytes = stackalloc byte[NumBytes];
if (StringUtils.TryParseHexString(text, bytes))
{
result = new BinaryId(bytes);
return true;
}
else
{
result = default;
return false;
}
}
///
/// Attempt to parse a binary id from a string
///
public static bool TryParse(ReadOnlySpan text, out BinaryId result)
{
Span bytes = stackalloc byte[NumBytes];
if (StringUtils.TryParseHexString(text, bytes))
{
result = new BinaryId(bytes);
return true;
}
else
{
result = default;
return false;
}
}
///
/// Checks whether this BinaryId is set
///
public bool IsEmpty => (_a | _b | _c) == 0;
///
public override bool Equals(object? obj) => obj is BinaryId id && Equals(id);
///
public override int GetHashCode() => HashCode.Combine(_a, _b, _c);
///
public bool Equals(BinaryId other) => _a == other._a && _b == other._b && _c == other._c;
///
public override string ToString()
{
Span bytes = stackalloc byte[NumBytes];
ToByteArray(bytes);
return StringUtils.FormatHexString(bytes);
}
///
/// Format this id as a sequence of UTF8 characters
///
public Utf8String ToUtf8String()
{
byte[] data = new byte[NumChars];
ToUtf8String(data.AsSpan());
return new Utf8String(data);
}
///
/// Format this id as a sequence of UTF8 characters
///
public void ToUtf8String(Span chars)
{
StringUtils.FormatLittleEndianUtf8HexString((uint)_a, chars);
StringUtils.FormatLittleEndianUtf8HexString((uint)_b, chars[8..]);
StringUtils.FormatLittleEndianUtf8HexString((uint)_c, chars[16..]);
}
///
/// Format this id as a sequence of UTF8 characters
///
public byte[] ToByteArray()
{
byte[] bytes = new byte[NumBytes];
ToByteArray(bytes);
return bytes;
}
///
/// Format this id as a sequence of UTF8 characters
///
public void ToByteArray(Span bytes)
{
BinaryPrimitives.WriteInt32LittleEndian(bytes, _a);
BinaryPrimitives.WriteInt32LittleEndian(bytes[4..], _b);
BinaryPrimitives.WriteInt32LittleEndian(bytes[8..], _c);
}
///
public int CompareTo(BinaryId other)
{
int result = _a.CompareTo(other._a);
if (result == 0)
{
result = _b.CompareTo(other._b);
if (result == 0)
{
result = _c.CompareTo(other._c);
}
}
return result;
}
///
/// Compares two binary ids for equality
///
/// The first string id
/// Second string id
/// True if the two string ids are equal
public static bool operator ==(BinaryId left, BinaryId right) => left.Equals(right);
///
/// Compares two binary ids for inequality
///
/// The first string id
/// Second string id
/// True if the two string ids are not equal
public static bool operator !=(BinaryId left, BinaryId right) => !left.Equals(right);
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public static bool operator <(BinaryId left, BinaryId right) => left.CompareTo(right) < 0;
public static bool operator <=(BinaryId left, BinaryId right) => left.CompareTo(right) <= 0;
public static bool operator >(BinaryId left, BinaryId right) => left.CompareTo(right) > 0;
public static bool operator >=(BinaryId left, BinaryId right) => left.CompareTo(right) >= 0;
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
///
/// Class which serializes types
///
sealed class BinaryIdJsonConverter : JsonConverter
{
///
public override BinaryId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => BinaryId.Parse(reader.GetUtf8String());
///
public override void Write(Utf8JsonWriter writer, BinaryId value, JsonSerializerOptions options)
{
Span bytes = stackalloc byte[BinaryId.NumChars];
value.ToUtf8String(bytes);
writer.WriteStringValue(bytes);
}
}
///
/// Class which serializes types
///
sealed class BinaryIdTypeConverter : TypeConverter
{
///
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => sourceType == typeof(string);
///
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) => BinaryId.Parse((string)value);
///
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) => destinationType == typeof(string);
///
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) => ((BinaryId)value!).ToString();
}
}