Code/Cache/PropertyDescriptionsCache.cs
using Sandbox.Internal;
using System.Collections.Concurrent;
using Sandbox;
using System.Linq;

namespace SandbankDatabase;

/// <summary>
/// When we clone objects via reflection, we do so based on the available properties on the
/// class. We cache that information here, rather than deducing it on every clone.
/// </summary>
internal static class PropertyDescriptionsCache
{
	private static ConcurrentDictionary<string, PropertyDescription[]> _propertyDescriptionsCache = new();

	public static void WipeStaticFields()
	{
		_propertyDescriptionsCache = new();
	}

	public static bool DoesClassHaveUIDProperty( string classTypeName, object instance )
	{
		// If we have a record of it then it must do since we only cache valid types.
		if ( _propertyDescriptionsCache.TryGetValue( classTypeName, out var properties ) )
			return true;

		return GlobalGameNamespace.TypeLibrary.GetPropertyDescriptions( instance )
			.Where( prop => prop.Attributes.Any( a => a is Saved || a is AutoSaved ) )
			.Any( x => x.Name == "UID" );
	}

	/// <summary>
	/// Returns type information for all [Saved] and [AutoSaved] properties on this class instance.
	/// </summary>
	public static PropertyDescription[] GetPropertyDescriptionsForType( string classTypeName, object instance )
	{
		if ( _propertyDescriptionsCache.TryGetValue( classTypeName, out var properties ) )
			return properties;

		properties = GlobalGameNamespace.TypeLibrary.GetPropertyDescriptions( instance )
			.Where( prop => prop.Attributes.Any( a => a is Saved || a is AutoSaved ) )
			.ToArray();

		// Only cache if this is a valid type with a UID property.
		if ( properties.Any( x => x.Name == "UID" ) )
			_propertyDescriptionsCache[classTypeName] = properties;

		return properties;
	}
}