Skip to content

Commit 539e617

Browse files
committed
Port ObjectPool<T> type from Roslyn
1 parent 525cf04 commit 539e617

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<Compile Include="$(MSBuildThisFileDirectory)Extensions\TypeDeclarationSyntaxExtensions.cs" />
7171
<Compile Include="$(MSBuildThisFileDirectory)Helpers\Comparer{T,TSelf}.cs" />
7272
<Compile Include="$(MSBuildThisFileDirectory)Helpers\HashCode.cs" />
73+
<Compile Include="$(MSBuildThisFileDirectory)Helpers\ObjectPool{T}.cs" />
7374
<Compile Include="$(MSBuildThisFileDirectory)Input\Models\CanExecuteExpressionType.cs" />
7475
<Compile Include="$(MSBuildThisFileDirectory)Input\Models\CommandInfo.cs" />
7576
<Compile Include="$(MSBuildThisFileDirectory)Input\RelayCommandGenerator.cs" />
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
// Ported from Roslyn, see: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs.
6+
7+
using System;
8+
using System.Runtime.CompilerServices;
9+
using System.Threading;
10+
11+
namespace Microsoft.CodeAnalysis.PooledObjects;
12+
13+
/// <summary>
14+
/// <para>
15+
/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose
16+
/// is that limited number of frequently used objects can be kept in the pool for further recycling.
17+
/// </para>
18+
/// <para>
19+
/// Notes:
20+
/// <list type="number">
21+
/// <item>
22+
/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there
23+
/// is no space in the pool, extra returned objects will be dropped.
24+
/// </item>
25+
/// <item>
26+
/// It is implied that if object was obtained from a pool, the caller will return it back in
27+
/// a relatively short time. Keeping checked out objects for long durations is ok, but
28+
/// reduces usefulness of pooling. Just new up your own.
29+
/// </item>
30+
/// </list>
31+
/// </para>
32+
/// <para>
33+
/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
34+
/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new".
35+
/// </para>
36+
/// </summary>
37+
/// <typeparam name="T">The type of objects to pool.</typeparam>
38+
internal sealed class ObjectPool<T>
39+
where T : class
40+
{
41+
/// <summary>
42+
/// The factory is stored for the lifetime of the pool. We will call this only when pool needs to
43+
/// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()".
44+
/// </summary>
45+
private readonly Func<T> factory;
46+
47+
/// <summary>
48+
/// The array of cached items.
49+
/// </summary>
50+
private readonly Element[] items;
51+
52+
/// <summary>
53+
/// Storage for the pool objects. The first item is stored in a dedicated field
54+
/// because we expect to be able to satisfy most requests from it.
55+
/// </summary>
56+
private T? firstItem;
57+
58+
/// <summary>
59+
/// Creates a new <see cref="ObjectPool{T}"/> instance with the specified parameters.
60+
/// </summary>
61+
/// <param name="factory">The input factory to produce <typeparamref name="T"/> items.</param>
62+
public ObjectPool(Func<T> factory)
63+
: this(factory, Environment.ProcessorCount * 2)
64+
{
65+
}
66+
67+
/// <summary>
68+
/// Creates a new <see cref="ObjectPool{T}"/> instance with the specified parameters.
69+
/// </summary>
70+
/// <param name="factory">The input factory to produce <typeparamref name="T"/> items.</param>
71+
/// <param name="size">The pool size to use.</param>
72+
public ObjectPool(Func<T> factory, int size)
73+
{
74+
this.factory = factory;
75+
this.items = new Element[size - 1];
76+
}
77+
78+
/// <summary>
79+
/// Produces a <typeparamref name="T"/> instance.
80+
/// </summary>
81+
/// <returns>The returned <typeparamref name="T"/> item to use.</returns>
82+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
83+
public T Allocate()
84+
{
85+
T? item = this.firstItem;
86+
87+
if (item is null || item != Interlocked.CompareExchange(ref this.firstItem, null, item))
88+
{
89+
item = AllocateSlow();
90+
}
91+
92+
return item;
93+
}
94+
95+
/// <summary>
96+
/// Returns a given <typeparamref name="T"/> instance to the pool.
97+
/// </summary>
98+
/// <param name="obj">The <typeparamref name="T"/> instance to return.</param>
99+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
100+
public void Free(T obj)
101+
{
102+
if (this.firstItem is null)
103+
{
104+
this.firstItem = obj;
105+
}
106+
else
107+
{
108+
FreeSlow(obj);
109+
}
110+
}
111+
112+
/// <summary>
113+
/// Allocates a new <typeparamref name="T"/> item.
114+
/// </summary>
115+
/// <returns>The returned <typeparamref name="T"/> item to use.</returns>
116+
[MethodImpl(MethodImplOptions.NoInlining)]
117+
private T AllocateSlow()
118+
{
119+
foreach (ref Element element in this.items.AsSpan())
120+
{
121+
T? instance = element.Value;
122+
123+
if (instance is not null)
124+
{
125+
if (instance == Interlocked.CompareExchange(ref element.Value, null, instance))
126+
{
127+
return instance;
128+
}
129+
}
130+
}
131+
132+
return this.factory();
133+
}
134+
135+
/// <summary>
136+
/// Frees a given <typeparamref name="T"/> item.
137+
/// </summary>
138+
/// <param name="obj">The <typeparamref name="T"/> item to return to the pool.</param>
139+
[MethodImpl(MethodImplOptions.NoInlining)]
140+
private void FreeSlow(T obj)
141+
{
142+
foreach (ref Element element in this.items.AsSpan())
143+
{
144+
if (element.Value is null)
145+
{
146+
element.Value = obj;
147+
148+
break;
149+
}
150+
}
151+
}
152+
153+
/// <summary>
154+
/// A container for a produced item (using a <see langword="struct"/> wrapper to avoid covariance checks).
155+
/// </summary>
156+
private struct Element
157+
{
158+
/// <summary>
159+
/// The value held at the current element.
160+
/// </summary>
161+
internal T? Value;
162+
}
163+
}

0 commit comments

Comments
 (0)