// 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;
}
}
}
}