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

namespace SandbankDatabase;

/// <summary>
/// Provides class instances so that we don't need to create instances on-the-fly,
/// which is a major performance bottleneck.
/// </summary>
internal static class ObjectPool
{
	private static ConcurrentDictionary<string, PoolTypeDefinition> _objectPool = new();

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

	public static T CloneObject<T>( T theObject, string classTypeName ) where T : class, new()
	{
		var instance = GetInstance<T>( classTypeName );
		Cloning.CopyClassData( theObject, instance, classTypeName );
		return instance;
	}

	public static object CloneObject( object theObject, Type objectType )
	{
		var instance = GetInstance( objectType.FullName, objectType );
		Cloning.CopyClassData( theObject, instance, objectType.FullName );
		return instance;
	}

	public static object GetInstance( string classTypeName, Type classType )
	{
		if ( !_objectPool.ContainsKey(classTypeName) )
		{
			throw new SandbankException( $"there is no registered instance pool for the type {classTypeName} - " +
				"are you using the wrong class type for this collection?" );
		}

		if ( _objectPool[classTypeName].TypePool.TryTake( out var instance ) )
			return instance;

		// If we couldn't get an instance, then we just have to create a new one.
		return GlobalGameNamespace.TypeLibrary.Create<object>( classType );
	}

	public static T GetInstance<T>(string classType) where T : class, new()
	{
		if ( _objectPool[classType].TypePool.TryTake( out var instance ) )
			return (T)instance;

		// If we couldn't get an instance, then we just have to create a new one.
		return new T();
	}

	/// <summary>
	/// Tell the pool that we want to pool this class type.
	/// </summary>
	public static void TryRegisterType( string classTypeName, Type classType )
	{
		// Different collections might use the same type. So this is possible.
		if ( _objectPool.ContainsKey( classTypeName ) )
			return;

		_objectPool[classTypeName] = new PoolTypeDefinition
		{
			ObjectType = classType,
			TypePool = new ConcurrentBag<object>()
		};
	}

	public static void CheckPool()
	{
		foreach (var poolPair in _objectPool)
		{
			if ( ConfigController.CLASS_INSTANCE_POOL_SIZE - poolPair.Value.TypePool.Count >= ConfigController.CLASS_INSTANCE_POOL_SIZE / 2)
			{
				ReplenishPoolType( poolPair.Key, poolPair.Value.ObjectType );
			}
		}
	}

	private static void ReplenishPoolType(string classTypeName, Type classType )
	{
		var concurrentList = _objectPool[classTypeName].TypePool;
		int instancesToCreate = ConfigController.CLASS_INSTANCE_POOL_SIZE - concurrentList.Count;

		for ( int i = 0; i < instancesToCreate; i++ )
		{
			concurrentList.Add(GlobalGameNamespace.TypeLibrary.Create<object>( classType ));
		}
	}
}

internal struct PoolTypeDefinition
{
	public Type ObjectType;
	public ConcurrentBag<object> TypePool;
}