diff --git a/GFramework.Core.Abstractions/internals/IsExternalInit.cs b/GFramework.Core.Abstractions/internals/IsExternalInit.cs new file mode 100644 index 0000000..880389a --- /dev/null +++ b/GFramework.Core.Abstractions/internals/IsExternalInit.cs @@ -0,0 +1,18 @@ +// IsExternalInit.cs +// This type is required to support init-only setters and record types +// when targeting netstandard2.0 or older frameworks. + +#if !NET5_0_OR_GREATER +using System.ComponentModel; + +namespace System.Runtime.CompilerServices; + +/// +/// 提供一个占位符类型,用于支持 C# 9.0 的 init 访问器功能。 +/// 该类型在 .NET 5.0 及更高版本中已内置,因此仅在较低版本的 .NET 中定义。 +/// +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit +{ +} +#endif \ No newline at end of file diff --git a/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs b/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs index c7c8b30..4f73a78 100644 --- a/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs +++ b/GFramework.Core.Abstractions/pool/IObjectPoolSystem.cs @@ -27,4 +27,39 @@ public interface IObjectPoolSystem /// 清空所有对象池 /// void Clear(); + + /// + /// 获取指定池的当前大小 + /// + /// 对象池的键 + /// 池中可用对象的数量 + int GetPoolSize(TKey key); + + /// + /// 获取指定池的活跃对象数量 + /// + /// 对象池的键 + /// 已被获取但未释放的对象数量 + int GetActiveCount(TKey key); + + /// + /// 设置指定池的最大容量 + /// + /// 对象池的键 + /// 最大容量,超过此容量的对象将被销毁而不是放回池中 + void SetMaxCapacity(TKey key, int maxCapacity); + + /// + /// 预热对象池,提前创建指定数量的对象 + /// + /// 对象池的键 + /// 要预创建的对象数量 + void Prewarm(TKey key, int count); + + /// + /// 获取指定池的统计信息 + /// + /// 对象池的键 + /// 池的统计信息 + PoolStatistics GetStatistics(TKey key); } \ No newline at end of file diff --git a/GFramework.Core.Abstractions/pool/PoolStatistics.cs b/GFramework.Core.Abstractions/pool/PoolStatistics.cs new file mode 100644 index 0000000..45bfdc5 --- /dev/null +++ b/GFramework.Core.Abstractions/pool/PoolStatistics.cs @@ -0,0 +1,42 @@ +namespace GFramework.Core.Abstractions.pool; + +/// +/// 对象池统计信息 +/// +public class PoolStatistics +{ + /// + /// 池中当前可用对象数量 + /// + public int AvailableCount { get; init; } + + /// + /// 当前活跃(已获取但未释放)的对象数量 + /// + public int ActiveCount { get; init; } + + /// + /// 池的最大容量限制,0 表示无限制 + /// + public int MaxCapacity { get; init; } + + /// + /// 累计创建的对象总数 + /// + public int TotalCreated { get; init; } + + /// + /// 累计获取对象的次数 + /// + public int TotalAcquired { get; init; } + + /// + /// 累计释放对象的次数 + /// + public int TotalReleased { get; init; } + + /// + /// 累计销毁的对象数量(超过容量限制被销毁的对象) + /// + public int TotalDestroyed { get; init; } +} diff --git a/GFramework.Core.Tests/extensions/ArrayPoolExtensionsTests.cs b/GFramework.Core.Tests/extensions/ArrayPoolExtensionsTests.cs new file mode 100644 index 0000000..fee502f --- /dev/null +++ b/GFramework.Core.Tests/extensions/ArrayPoolExtensionsTests.cs @@ -0,0 +1,194 @@ +using System.Buffers; +using GFramework.Core.extensions; +using NUnit.Framework; + +namespace GFramework.Core.Tests.extensions; + +/// +/// 测试 ArrayPoolExtensions 的功能 +/// +[TestFixture] +public class ArrayPoolExtensionsTests +{ + private ArrayPool _pool = null!; + + [SetUp] + public void SetUp() + { + _pool = ArrayPool.Shared; + } + + [Test] + public void RentArray_Should_Return_Array() + { + // Act + var array = _pool.RentArray(100); + + // Assert + Assert.That(array, Is.Not.Null); + Assert.That(array.Length, Is.GreaterThanOrEqualTo(100)); + + // Cleanup + _pool.ReturnArray(array); + } + + [Test] + public void ReturnArray_Should_Not_Throw() + { + // Arrange + var array = _pool.RentArray(100); + + // Act & Assert + Assert.DoesNotThrow(() => _pool.ReturnArray(array)); + } + + [Test] + public void ReturnArray_With_Clear_Should_Clear_Array() + { + // Arrange + var array = _pool.RentArray(10); + array[0] = 42; + array[5] = 99; + + // Act + _pool.ReturnArray(array, clearArray: true); + + // Assert + Assert.That(array[0], Is.EqualTo(0)); + Assert.That(array[5], Is.EqualTo(0)); + } + + [Test] + public void GetScoped_Should_Return_Disposable_Wrapper() + { + // Act + using var scoped = _pool.GetScoped(100); + + // Assert + Assert.That(scoped.Array, Is.Not.Null); + Assert.That(scoped.Array.Length, Is.GreaterThanOrEqualTo(100)); + Assert.That(scoped.Length, Is.GreaterThanOrEqualTo(100)); + } + + [Test] + public void GetScoped_Should_Auto_Return_On_Dispose() + { + // Arrange & Act + int[] array; + using (var scoped = _pool.GetScoped(100)) + { + array = scoped.Array; + array[0] = 42; + } + + // Assert - 如果没有异常就说明正常归还了 + Assert.Pass(); + } + + [Test] + public void ScopedArray_AsSpan_Should_Return_Span() + { + // Arrange + using var scoped = _pool.GetScoped(100); + + // Act + var span = scoped.AsSpan(); + + // Assert + Assert.That(span.Length, Is.EqualTo(scoped.Length)); + } + + [Test] + public void ScopedArray_AsSpan_With_Range_Should_Return_Slice() + { + // Arrange + using var scoped = _pool.GetScoped(100); + + // Act + var span = scoped.AsSpan(10, 20); + + // Assert + Assert.That(span.Length, Is.EqualTo(20)); + } + + [Test] + public void GetScoped_With_ClearOnReturn_Should_Clear_Array() + { + // Arrange + int[] array; + using (var scoped = _pool.GetScoped(10, clearOnReturn: true)) + { + array = scoped.Array; + array[0] = 42; + array[5] = 99; + } + + // Assert + Assert.That(array[0], Is.EqualTo(0)); + Assert.That(array[5], Is.EqualTo(0)); + } + + [Test] + public void Multiple_Scoped_Arrays_Should_Work_Independently() + { + // Act + using var scoped1 = _pool.GetScoped(50); + using var scoped2 = _pool.GetScoped(100); + + scoped1.Array[0] = 1; + scoped2.Array[0] = 2; + + // Assert + Assert.That(scoped1.Array[0], Is.EqualTo(1)); + Assert.That(scoped2.Array[0], Is.EqualTo(2)); + Assert.That(scoped1.Array, Is.Not.SameAs(scoped2.Array)); + } + + [Test] + public void RentArray_Should_Be_Reusable() + { + // Arrange + var array1 = _pool.RentArray(100); + array1[0] = 42; + _pool.ReturnArray(array1, clearArray: true); + + // Act + var array2 = _pool.RentArray(100); + + // Assert + // 可能是同一个数组(已清空),也可能是不同的数组 + Assert.That(array2, Is.Not.Null); + Assert.That(array2.Length, Is.GreaterThanOrEqualTo(100)); + + // Cleanup + _pool.ReturnArray(array2); + } + + [Test] + public void ScopedArray_Should_Work_With_Using_Declaration() + { + // Act + using var scoped = _pool.GetScoped(50); + scoped.Array[0] = 123; + + // Assert + Assert.That(scoped.Array[0], Is.EqualTo(123)); + } + + [Test] + public void AsSpan_Should_Allow_Span_Operations() + { + // Arrange + using var scoped = _pool.GetScoped(10); + var span = scoped.AsSpan(); + + // Act + span.Fill(42); + + // Assert + for (var i = 0; i < span.Length; i++) + { + Assert.That(span[i], Is.EqualTo(42)); + } + } +} diff --git a/GFramework.Core.Tests/pool/ObjectPoolTests.cs b/GFramework.Core.Tests/pool/ObjectPoolTests.cs index b3fa16c..0ce50d9 100644 --- a/GFramework.Core.Tests/pool/ObjectPoolTests.cs +++ b/GFramework.Core.Tests/pool/ObjectPoolTests.cs @@ -119,6 +119,137 @@ public class ObjectPoolTests var obj2 = _pool.Acquire("test"); Assert.That(obj2.OnAcquireCalled, Is.True); } + + /// + /// 验证GetPoolSize应该返回正确的池大小 + /// + [Test] + public void GetPoolSize_Should_Return_Correct_Size() + { + // Arrange + var obj1 = _pool.Acquire("test"); + var obj2 = _pool.Acquire("test"); + + // Act & Assert + Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(0)); + + _pool.Release("test", obj1); + Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(1)); + + _pool.Release("test", obj2); + Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(2)); + } + + /// + /// 验证GetActiveCount应该返回正确的活跃对象数量 + /// + [Test] + public void GetActiveCount_Should_Return_Correct_Count() + { + // Arrange & Act & Assert + Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(0)); + + var obj1 = _pool.Acquire("test"); + Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(1)); + + var obj2 = _pool.Acquire("test"); + Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(2)); + + _pool.Release("test", obj1); + Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(1)); + + _pool.Release("test", obj2); + Assert.That(_pool.GetActiveCount("test"), Is.EqualTo(0)); + } + + /// + /// 验证SetMaxCapacity应该限制池的最大容量 + /// + [Test] + public void SetMaxCapacity_Should_Limit_Pool_Size() + { + // Arrange + _pool.SetMaxCapacity("test", 2); + var obj1 = _pool.Acquire("test"); + var obj2 = _pool.Acquire("test"); + var obj3 = _pool.Acquire("test"); + + // Act + _pool.Release("test", obj1); + _pool.Release("test", obj2); + _pool.Release("test", obj3); + + // Assert + Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(2)); + Assert.That(obj3.OnPoolDestroyCalled, Is.True); + } + + /// + /// 验证Prewarm应该预创建指定数量的对象 + /// + [Test] + public void Prewarm_Should_Create_Objects_In_Advance() + { + // Act + _pool.Prewarm("test", 5); + + // Assert + Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(5)); + + var obj = _pool.Acquire("test"); + Assert.That(_pool.GetPoolSize("test"), Is.EqualTo(4)); + Assert.That(obj.OnReleaseCalled, Is.True); // 预热时调用了OnRelease + } + + /// + /// 验证GetStatistics应该返回正确的统计信息 + /// + [Test] + public void GetStatistics_Should_Return_Correct_Statistics() + { + // Arrange + _pool.SetMaxCapacity("test", 2); + _pool.Prewarm("test", 2); + + var obj1 = _pool.Acquire("test"); + var obj2 = _pool.Acquire("test"); + var obj3 = _pool.Acquire("test"); // 这个会创建新对象 + + _pool.Release("test", obj1); + _pool.Release("test", obj2); + _pool.Release("test", obj3); // 这个会被销毁 + + // Act + var stats = _pool.GetStatistics("test"); + + // Assert + Assert.That(stats.AvailableCount, Is.EqualTo(2)); + Assert.That(stats.ActiveCount, Is.EqualTo(0)); + Assert.That(stats.MaxCapacity, Is.EqualTo(2)); + Assert.That(stats.TotalCreated, Is.EqualTo(3)); // 2个预热 + 1个新创建 + Assert.That(stats.TotalAcquired, Is.EqualTo(3)); + Assert.That(stats.TotalReleased, Is.EqualTo(3)); + Assert.That(stats.TotalDestroyed, Is.EqualTo(1)); + } + + /// + /// 验证不存在的池应该返回空统计信息 + /// + [Test] + public void GetStatistics_Should_Return_Empty_For_Nonexistent_Pool() + { + // Act + var stats = _pool.GetStatistics("nonexistent"); + + // Assert + Assert.That(stats.AvailableCount, Is.EqualTo(0)); + Assert.That(stats.ActiveCount, Is.EqualTo(0)); + Assert.That(stats.MaxCapacity, Is.EqualTo(0)); + Assert.That(stats.TotalCreated, Is.EqualTo(0)); + Assert.That(stats.TotalAcquired, Is.EqualTo(0)); + Assert.That(stats.TotalReleased, Is.EqualTo(0)); + Assert.That(stats.TotalDestroyed, Is.EqualTo(0)); + } } /// diff --git a/GFramework.Core.Tests/pool/StringBuilderPoolTests.cs b/GFramework.Core.Tests/pool/StringBuilderPoolTests.cs new file mode 100644 index 0000000..66cde30 --- /dev/null +++ b/GFramework.Core.Tests/pool/StringBuilderPoolTests.cs @@ -0,0 +1,116 @@ +using GFramework.Core.pool; +using NUnit.Framework; + +namespace GFramework.Core.Tests.pool; + +/// +/// 测试 StringBuilderPool 的功能 +/// +[TestFixture] +public class StringBuilderPoolTests +{ + [Test] + public void Rent_Should_Return_StringBuilder() + { + // Act + var sb = StringBuilderPool.Rent(); + + // Assert + Assert.That(sb, Is.Not.Null); + Assert.That(sb.Length, Is.EqualTo(0)); + } + + [Test] + public void Rent_Should_Respect_Capacity() + { + // Act + var sb = StringBuilderPool.Rent(512); + + // Assert + Assert.That(sb.Capacity, Is.GreaterThanOrEqualTo(512)); + } + + [Test] + public void Return_Should_Clear_StringBuilder() + { + // Arrange + var sb = StringBuilderPool.Rent(); + sb.Append("Hello World"); + + // Act + StringBuilderPool.Return(sb); + + // Assert + Assert.That(sb.Length, Is.EqualTo(0)); + } + + [Test] + public void Return_Should_Not_Throw_For_Large_Capacity() + { + // Arrange + var sb = StringBuilderPool.Rent(10000); + + // Act & Assert + Assert.DoesNotThrow(() => StringBuilderPool.Return(sb)); + } + + [Test] + public void GetScoped_Should_Return_Disposable_Wrapper() + { + // Act + using var scoped = StringBuilderPool.GetScoped(); + + // Assert + Assert.That(scoped.Value, Is.Not.Null); + Assert.That(scoped.Value.Length, Is.EqualTo(0)); + } + + [Test] + public void GetScoped_Should_Auto_Return_On_Dispose() + { + // Arrange + var sb = StringBuilderPool.GetScoped().Value; + sb.Append("Test"); + + // Act + using (var scoped = StringBuilderPool.GetScoped()) + { + scoped.Value.Append("Hello"); + } + + // Assert - 如果没有异常就说明正常归还了 + Assert.Pass(); + } + + [Test] + public void StringBuilder_Should_Be_Reusable() + { + // Arrange + var sb = StringBuilderPool.Rent(); + sb.Append("First use"); + StringBuilderPool.Return(sb); + + // Act + sb.Append("Second use"); + + // Assert + Assert.That(sb.ToString(), Is.EqualTo("Second use")); + } + + [Test] + public void GetScoped_With_Using_Should_Work() + { + // Act + string result; + using (var scoped = StringBuilderPool.GetScoped()) + { + scoped.Value.Append("Hello"); + scoped.Value.Append(" "); + scoped.Value.Append("World"); + result = scoped.Value.ToString(); + } + + // Assert + Assert.That(result, Is.EqualTo("Hello World")); + } +} diff --git a/GFramework.Core/extensions/ArrayPoolExtensions.cs b/GFramework.Core/extensions/ArrayPoolExtensions.cs new file mode 100644 index 0000000..3a2d98f --- /dev/null +++ b/GFramework.Core/extensions/ArrayPoolExtensions.cs @@ -0,0 +1,136 @@ +using System.Buffers; + +namespace GFramework.Core.extensions; + +/// +/// ArrayPool 扩展方法,提供更便捷的数组池操作 +/// +public static class ArrayPoolExtensions +{ + /// + /// 从数组池中租用数组 + /// + /// 数组元素类型 + /// 数组池实例 + /// 最小长度 + /// 租用的数组 + /// + /// + /// var pool = ArrayPool<int>.Shared; + /// var array = pool.RentArray(100); + /// try + /// { + /// // 使用数组 + /// } + /// finally + /// { + /// pool.ReturnArray(array); + /// } + /// + /// + public static T[] RentArray(this ArrayPool pool, int minimumLength) + { + ArgumentNullException.ThrowIfNull(pool); + return pool.Rent(minimumLength); + } + + /// + /// 将数组归还到数组池 + /// + /// 数组元素类型 + /// 数组池实例 + /// 要归还的数组 + /// 是否清空数组内容 + /// + /// + /// var pool = ArrayPool<int>.Shared; + /// var array = pool.RentArray(100); + /// try + /// { + /// // 使用数组 + /// } + /// finally + /// { + /// pool.ReturnArray(array, clearArray: true); + /// } + /// + /// + public static void ReturnArray(this ArrayPool pool, T[] array, bool clearArray = false) + { + ArgumentNullException.ThrowIfNull(pool); + ArgumentNullException.ThrowIfNull(array); + pool.Return(array, clearArray); + } + + /// + /// 获取一个作用域数组,使用完后自动归还 + /// + /// 数组元素类型 + /// 数组池实例 + /// 最小长度 + /// 归还时是否清空数组 + /// 可自动释放的数组包装器 + /// + /// + /// var pool = ArrayPool<int>.Shared; + /// using var scopedArray = pool.GetScoped(100); + /// var array = scopedArray.Array; + /// // 使用数组 + /// // 自动归还 + /// + /// + public static ScopedArray GetScoped(this ArrayPool pool, int minimumLength, bool clearOnReturn = false) + { + ArgumentNullException.ThrowIfNull(pool); + return new ScopedArray(pool, minimumLength, clearOnReturn); + } + + /// + /// 可自动释放的数组包装器 + /// + /// 数组元素类型 + public readonly struct ScopedArray : IDisposable + { + private readonly ArrayPool _pool; + private readonly bool _clearOnReturn; + + /// + /// 获取租用的数组 + /// + public T[] Array { get; } + + /// + /// 获取数组的长度 + /// + public int Length => Array.Length; + + internal ScopedArray(ArrayPool pool, int minimumLength, bool clearOnReturn) + { + _pool = pool; + _clearOnReturn = clearOnReturn; + Array = pool.Rent(minimumLength); + } + + /// + /// 释放数组并归还到池中 + /// + public void Dispose() + { + _pool.Return(Array, _clearOnReturn); + } + + /// + /// 获取数组的 Span 视图 + /// + /// 数组的 Span + public Span AsSpan() => Array.AsSpan(); + + /// + /// 获取数组指定范围的 Span 视图 + /// + /// 起始索引 + /// 长度 + /// 数组指定范围的 Span + public Span AsSpan(int start, int length) => Array.AsSpan(start, length); + } +} diff --git a/GFramework.Core/pool/AbstractObjectPoolSystem.cs b/GFramework.Core/pool/AbstractObjectPoolSystem.cs index a5b8110..95d0b93 100644 --- a/GFramework.Core/pool/AbstractObjectPoolSystem.cs +++ b/GFramework.Core/pool/AbstractObjectPoolSystem.cs @@ -12,9 +12,52 @@ public abstract class AbstractObjectPoolSystem : AbstractSystem, IObjectPoolSystem where TObject : IPoolableObject where TKey : notnull { /// - /// 存储对象池的字典,键为池标识,值为对应类型的对象栈 + /// 池信息类,用于管理对象池的核心数据结构和统计信息。 + /// 包含对象栈、容量限制以及各类操作的统计计数。 /// - protected readonly Dictionary> Pools = new(); + protected class PoolInfo + { + /// + /// 对象栈,用于存储可复用的对象实例。 + /// + public Stack Stack { get; } = new(); + + /// + /// 池的最大容量限制,超过此数量时将不再创建新对象。 + /// + public int MaxCapacity { get; set; } + + /// + /// 总共创建的对象数量统计。 + /// + public int TotalCreated { get; set; } + + /// + /// 总共从池中获取的对象数量统计。 + /// + public int TotalAcquired { get; set; } + + /// + /// 总共归还到池中的对象数量统计。 + /// + public int TotalReleased { get; set; } + + /// + /// 总共销毁的对象数量统计。 + /// + public int TotalDestroyed { get; set; } + + /// + /// 当前活跃(正在使用)的对象数量统计。 + /// + public int ActiveCount { get; set; } + } + + + /// + /// 存储对象池的字典,键为池标识,值为池信息 + /// + protected readonly Dictionary Pools = new(); /// /// 获取对象池中的对象,如果池中没有可用对象则创建新的对象 @@ -23,16 +66,25 @@ public abstract class AbstractObjectPoolSystem /// 获取到的对象实例 public TObject Acquire(TKey key) { - if (!Pools.TryGetValue(key, out var pool)) + if (!Pools.TryGetValue(key, out var poolInfo)) { - pool = new Stack(); - Pools[key] = pool; + poolInfo = new PoolInfo(); + Pools[key] = poolInfo; } - var obj = pool.Count > 0 - ? pool.Pop() - : Create(key); + TObject obj; + if (poolInfo.Stack.Count > 0) + { + obj = poolInfo.Stack.Pop(); + } + else + { + obj = Create(key); + poolInfo.TotalCreated++; + } + poolInfo.TotalAcquired++; + poolInfo.ActiveCount++; obj.OnAcquire(); return obj; } @@ -46,13 +98,26 @@ public abstract class AbstractObjectPoolSystem { obj.OnRelease(); - if (!Pools.TryGetValue(key, out var pool)) + if (!Pools.TryGetValue(key, out var poolInfo)) { - pool = new Stack(); - Pools[key] = pool; + poolInfo = new PoolInfo(); + Pools[key] = poolInfo; } - pool.Push(obj); + poolInfo.TotalReleased++; + poolInfo.ActiveCount--; + + // 检查容量限制 + if (poolInfo.MaxCapacity > 0 && poolInfo.Stack.Count >= poolInfo.MaxCapacity) + { + // 超过容量限制,销毁对象 + obj.OnPoolDestroy(); + poolInfo.TotalDestroyed++; + } + else + { + poolInfo.Stack.Push(obj); + } } /// @@ -61,11 +126,108 @@ public abstract class AbstractObjectPoolSystem public void Clear() { // 遍历所有对象池,调用每个对象的销毁方法 - foreach (var obj in Pools.Values.SelectMany(pool => pool)) obj.OnPoolDestroy(); + foreach (var poolInfo in Pools.Values) + { + foreach (var obj in poolInfo.Stack) + { + obj.OnPoolDestroy(); + } + } Pools.Clear(); } + /// + /// 获取指定池的当前大小 + /// + /// 对象池的键 + /// 池中可用对象的数量 + public int GetPoolSize(TKey key) + { + return Pools.TryGetValue(key, out var poolInfo) ? poolInfo.Stack.Count : 0; + } + + /// + /// 获取指定池的活跃对象数量 + /// + /// 对象池的键 + /// 已被获取但未释放的对象数量 + public int GetActiveCount(TKey key) + { + return Pools.TryGetValue(key, out var poolInfo) ? poolInfo.ActiveCount : 0; + } + + /// + /// 设置指定池的最大容量 + /// + /// 对象池的键 + /// 最大容量,超过此容量的对象将被销毁而不是放回池中 + public void SetMaxCapacity(TKey key, int maxCapacity) + { + if (!Pools.TryGetValue(key, out var poolInfo)) + { + poolInfo = new PoolInfo(); + Pools[key] = poolInfo; + } + + poolInfo.MaxCapacity = maxCapacity; + } + + /// + /// 预热对象池,提前创建指定数量的对象 + /// + /// 对象池的键 + /// 要预创建的对象数量 + public void Prewarm(TKey key, int count) + { + if (!Pools.TryGetValue(key, out var poolInfo)) + { + poolInfo = new PoolInfo(); + Pools[key] = poolInfo; + } + + for (var i = 0; i < count; i++) + { + var obj = Create(key); + poolInfo.TotalCreated++; + obj.OnRelease(); + poolInfo.Stack.Push(obj); + } + } + + /// + /// 获取指定池的统计信息 + /// + /// 对象池的键 + /// 池的统计信息 + public PoolStatistics GetStatistics(TKey key) + { + if (!Pools.TryGetValue(key, out var poolInfo)) + { + return new PoolStatistics + { + AvailableCount = 0, + ActiveCount = 0, + MaxCapacity = 0, + TotalCreated = 0, + TotalAcquired = 0, + TotalReleased = 0, + TotalDestroyed = 0 + }; + } + + return new PoolStatistics + { + AvailableCount = poolInfo.Stack.Count, + ActiveCount = poolInfo.ActiveCount, + MaxCapacity = poolInfo.MaxCapacity, + TotalCreated = poolInfo.TotalCreated, + TotalAcquired = poolInfo.TotalAcquired, + TotalReleased = poolInfo.TotalReleased, + TotalDestroyed = poolInfo.TotalDestroyed + }; + } + /// /// 创建一个新的对象实例(由子类决定怎么创建) /// diff --git a/GFramework.Core/pool/StringBuilderPool.cs b/GFramework.Core/pool/StringBuilderPool.cs new file mode 100644 index 0000000..ff49027 --- /dev/null +++ b/GFramework.Core/pool/StringBuilderPool.cs @@ -0,0 +1,111 @@ +using System.Text; + +namespace GFramework.Core.pool; + +/// +/// StringBuilder 对象池,提供高性能的字符串构建器复用 +/// +public static class StringBuilderPool +{ + private const int DefaultCapacity = 256; + private const int MaxRetainedCapacity = 4096; + + /// + /// 从池中租用一个 StringBuilder + /// + /// 初始容量,默认为 256 + /// StringBuilder 实例 + /// + /// + /// var sb = StringBuilderPool.Rent(); + /// try + /// { + /// sb.Append("Hello"); + /// sb.Append(" World"); + /// return sb.ToString(); + /// } + /// finally + /// { + /// StringBuilderPool.Return(sb); + /// } + /// + /// + public static StringBuilder Rent(int capacity = DefaultCapacity) + { + var sb = new StringBuilder(capacity); + return sb; + } + + /// + /// 将 StringBuilder 归还到池中 + /// + /// 要归还的 StringBuilder + /// + /// + /// var sb = StringBuilderPool.Rent(); + /// try + /// { + /// sb.Append("Hello World"); + /// Console.WriteLine(sb.ToString()); + /// } + /// finally + /// { + /// StringBuilderPool.Return(sb); + /// } + /// + /// + public static void Return(StringBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + + // 如果容量过大,不放回池中 + if (builder.Capacity > MaxRetainedCapacity) + { + return; + } + + builder.Clear(); + } + + /// + /// 获取一个 StringBuilder,使用完后自动归还 + /// + /// 初始容量 + /// 可自动释放的 StringBuilder 包装器 + /// + /// + /// using var sb = StringBuilderPool.GetScoped(); + /// sb.Value.Append("Hello"); + /// sb.Value.Append(" World"); + /// return sb.Value.ToString(); + /// + /// + public static ScopedStringBuilder GetScoped(int capacity = DefaultCapacity) + { + return new ScopedStringBuilder(Rent(capacity)); + } + + /// + /// 可自动释放的 StringBuilder 包装器 + /// + public readonly struct ScopedStringBuilder : IDisposable + { + /// + /// 获取 StringBuilder 实例 + /// + public StringBuilder Value { get; } + + internal ScopedStringBuilder(StringBuilder value) + { + Value = value; + } + + /// + /// 释放 StringBuilder 并归还到池中 + /// + public void Dispose() + { + Return(Value); + } + } +}