// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.ComponentModel; using System.Diagnostics; using System.Globalization; namespace EpicGames.Horde.Jobs { /// /// Identifier for subresources. Assigning unique ids to subresources prevents against race conditions using indices when subresources are added and removed. /// /// Subresource identifiers are stored as 16-bit integers formatted as a 4-digit hex code, in order to keep URLs short. Calling Next() will generate a new /// identifier with more entropy than just incrementing the value but an identical period before repeating, in order to make URL fragments more distinctive. /// [TypeConverter(typeof(SubResourceIdTypeConverter))] public readonly struct SubResourceId : IEquatable { /// /// The unique identifier value /// public ushort Value { get; } /// /// Constructor /// /// New identifier for this subresource public SubResourceId(ushort value) { Value = value; } /// /// Creates a new random subresource id. We use random numbers for this to increase distinctiveness. /// /// New subresource id public static SubResourceId GenerateNewId() { return new SubResourceId((ushort)Stopwatch.GetTimestamp()); } /// /// Updates the current value, and returns a copy of the previous value. /// /// New subresource identifier public SubResourceId Next() { // 771 is a factor of 65535, so we won't repeat when wrapping round 64k. return new SubResourceId((ushort)((int)Value + 771)); } /// /// Parse a subresource id from a string /// /// Text to parse /// New subresource id public static SubResourceId Parse(string text) { return new SubResourceId(UInt16.Parse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture)); } /// /// Attempt to parse a subresource id from a string /// /// Text to parse /// Receives the parsed subresource id on success /// True if the id was parsed correctly public static bool TryParse(string text, out SubResourceId subResourceId) { ushort result; if (UInt16.TryParse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result)) { subResourceId = new SubResourceId(result); return true; } else { subResourceId = default; return false; } } /// /// Converts this identifier to a string /// /// String representation of this id public override string ToString() { return Value.ToString("x4", CultureInfo.InvariantCulture); } /// public override bool Equals(object? other) { return other is SubResourceId otherId && Equals(otherId); } /// public bool Equals(SubResourceId other) { return Value == other.Value; } /// public override int GetHashCode() { return (int)Value; } /// /// Equality operator for identifiers /// /// First identifier to compare /// Second identifier to compare /// True if the identifiers are equal public static bool operator ==(SubResourceId left, SubResourceId right) { return left.Value == right.Value; } /// /// Inequality operator for identifiers /// /// First identifier to compare /// Second identifier to compare /// True if the identifiers are equal public static bool operator !=(SubResourceId left, SubResourceId right) { return left.Value != right.Value; } } /// /// Extension methods for manipulating subresource ids /// public static class SubResourceIdExtensions { /// /// Parse a string as a subresource identifier /// /// Text to parse /// The new subresource identifier public static SubResourceId ToSubResourceId(this string text) { return new SubResourceId(UInt16.Parse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture)); } } /// /// Type converter from strings to SubResourceId objects /// sealed class SubResourceIdTypeConverter : TypeConverter { /// public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } /// public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return SubResourceId.Parse((string)value); } /// public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) { return destinationType == typeof(string); } /// public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) { if (destinationType == typeof(string)) { return value?.ToString(); } else { return null; } } } }