Skip to content

Commit d661ad0

Browse files
committed
add pool helpers
1 parent ef362ec commit d661ad0

4 files changed

Lines changed: 203 additions & 8 deletions

File tree

src/Backdash/Core/Extensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public static double NextGaussian(this Random @this)
4545
public static bool NextBool(this Random random, double percentage) =>
4646
random.NextDouble() < Math.Clamp(percentage, 0.0, 1.0);
4747

48+
/// <inheritdoc cref="System.Random.Shuffle{T}(T[])"/>
49+
public static void Shuffle<T>(this Random random, List<T> values) =>
50+
random.Shuffle(CollectionsMarshal.AsSpan(values));
51+
4852
/// <summary>
4953
/// Fill <paramref name="value"/> with random values.
5054
/// </summary>

src/Backdash/Data/ObjectPool.cs

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections;
2+
using System.Collections.Immutable;
23
using System.Runtime.CompilerServices;
34
using System.Runtime.InteropServices;
45

@@ -15,10 +16,25 @@ public interface IObjectPool<T>
1516
/// </summary>
1617
T Rent();
1718

19+
/// <inheritdoc cref="Rent()"/>
20+
void Rent(ICollection<T> values, int count);
21+
1822
/// <summary>
1923
/// Return <paramref name="value" /> to the pool
2024
/// </summary>
2125
bool Return(T value);
26+
27+
/// <inheritdoc cref="Return(T)"/>
28+
bool Return(ref T? value);
29+
30+
/// <inheritdoc cref="Return(T)"/>
31+
bool ReturnAll(List<T> list);
32+
33+
/// <inheritdoc cref="Return(T)"/>
34+
bool ReturnMany(ReadOnlySpan<T> values);
35+
36+
/// <inheritdoc cref="Return(T)"/>
37+
bool ReturnMany(IEnumerable<T> values);
2238
}
2339

2440
/// <summary>
@@ -78,6 +94,13 @@ public T Rent()
7894
return item;
7995
}
8096

97+
/// <inheritdoc />
98+
public void Rent(ICollection<T> values, int count)
99+
{
100+
for (var i = 0; i < count; i++)
101+
values.Add(Rent());
102+
}
103+
81104
/// <inheritdoc />
82105
public bool Return(T value)
83106
{
@@ -101,26 +124,72 @@ public bool Return(T value)
101124
return true;
102125
}
103126

104-
/// <inheritdoc cref="Return"/>
105-
public bool ReturnMany(params T[] values)
127+
/// <inheritdoc />
128+
public bool Return(ref T? value)
129+
{
130+
if (value is null || !Return(value)) return false;
131+
value = null;
132+
return true;
133+
}
134+
135+
/// <inheritdoc cref="Return(T)"/>
136+
public bool ReturnAll(List<T> list)
106137
{
107138
var result = true;
139+
var values = CollectionsMarshal.AsSpan(list);
140+
ref var current = ref MemoryMarshal.GetReference(values);
141+
ref var limit = ref Unsafe.Add(ref current, values.Length);
108142

109-
ref var current = ref MemoryMarshal.GetReference(values.AsSpan());
143+
while (Unsafe.IsAddressLessThan(ref current, ref limit))
144+
{
145+
var returned = Return(current);
146+
ref var next = ref Unsafe.Add(ref current, 1)!;
147+
if (returned)
148+
current = null!;
149+
else
150+
result = false;
151+
152+
current = ref next;
153+
}
154+
155+
if (result)
156+
list.Clear();
157+
else
158+
list.RemoveAll(x => (object?)x is null);
159+
160+
return result;
161+
}
162+
163+
/// <inheritdoc cref="Return(T)"/>
164+
public bool ReturnMany(ReadOnlySpan<T> values)
165+
{
166+
var result = true;
167+
ref var current = ref MemoryMarshal.GetReference(values);
110168
ref var limit = ref Unsafe.Add(ref current, values.Length);
111169

112170
while (Unsafe.IsAddressLessThan(ref current, ref limit))
113171
{
114-
result = result && Return(current);
172+
var returned = Return(current);
173+
result = result && returned;
115174
current = ref Unsafe.Add(ref current, 1)!;
116175
}
117176

118177
return result;
119178
}
120179

121-
/// <inheritdoc cref="ReturnMany(T[])"/>
180+
/// <inheritdoc cref="ReturnMany(System.ReadOnlySpan{T})"/>
122181
public bool ReturnMany(IEnumerable<T> values) =>
123-
values.Aggregate(true, (result, current) => result && Return(current));
182+
values switch
183+
{
184+
T[] array => ReturnMany(array.AsSpan()),
185+
ImmutableArray<T> array => ReturnMany(array.AsSpan()),
186+
List<T> list => ReturnMany(CollectionsMarshal.AsSpan(list)),
187+
_ => values.Aggregate(true, (result, current) =>
188+
{
189+
var returned = Return(current);
190+
return result && returned;
191+
}),
192+
};
124193

125194
/// <summary>
126195
/// Clear the object pool
@@ -192,6 +261,23 @@ public static ObjectPool<T> Create<T>(int? capacity = null, Action<T>? returnWit
192261
/// </summary>
193262
public static ObjectPool<T> Singleton<T>() where T : class, new() => SingletonWrapper<T>.Instance;
194263

264+
/// <inheritdoc cref="IObjectPool{T}.Rent()"/>
265+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
266+
public static T Rent<T>() where T : class, new() => Singleton<T>().Rent();
267+
268+
/// <inheritdoc cref="IObjectPool{T}.Rent(ICollection{T}, int)"/>
269+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
270+
public static void Rent<T>(ICollection<T> values, int count) where T : class, new() =>
271+
Singleton<T>().Rent(values, count);
272+
273+
/// <inheritdoc cref="IObjectPool{T}.Return(T)"/>
274+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
275+
public static bool Return<T>(T value) where T : class, new() => Singleton<T>().Return(value);
276+
277+
/// <inheritdoc cref="IObjectPool{T}.Return(ref T)"/>
278+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
279+
public static bool Return<T>(ref T? value) where T : class, new() => Singleton<T>().Return(ref value);
280+
195281
static class SingletonWrapper<T> where T : class, new()
196282
{
197283
public static readonly ObjectPool<T> Instance = Create<T>();

tests/Backdash.Tests/Specs/Unit/Data/ObjectPoolTests.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ public void ShouldRentAndReturnSameObject()
2121
value2.Should().BeSameAs(value);
2222
}
2323

24+
[Fact]
25+
public void ShouldRentAndReturnSettingNullRefObject()
26+
{
27+
var sut = ObjectPool.Create<TestObj>();
28+
TestObj? value = sut.Rent();
29+
value.Should().NotBeNull();
30+
sut.Count.Should().Be(0);
31+
32+
sut.Return(ref value);
33+
sut.Count.Should().Be(1);
34+
value.Should().BeNull();
35+
}
36+
2437
[Fact]
2538
public void ShouldNotReturnSameObjectTwice()
2639
{
@@ -106,17 +119,67 @@ public void ShouldDisposeItems()
106119
rents.Should().AllSatisfy(r => r.Disposed.Should().BeTrue());
107120
}
108121

122+
[Fact]
123+
public void ShouldReturnAndClearList()
124+
{
125+
const int cap = 10;
126+
var sut = ObjectPool.Create<TestObj>();
127+
List<TestObj> buffer = new(cap);
128+
sut.Rent(buffer, cap);
129+
buffer.Count.Should().Be(cap);
130+
sut.Count.Should().Be(0);
131+
132+
sut.ReturnAll(buffer).Should().BeTrue();
133+
buffer.Count.Should().Be(0);
134+
sut.Count.Should().Be(cap);
135+
}
136+
137+
[Fact]
138+
public void ShouldReturnRepeatedListItems()
139+
{
140+
const int cap = 10;
141+
var sut = ObjectPool.Create<TestObj>();
142+
List<TestObj> buffer = new(cap);
143+
sut.Rent(buffer, cap);
144+
sut.Count.Should().Be(0);
145+
146+
var copy = buffer.ToList();
147+
Random.Shared.Shuffle(copy);
148+
buffer.AddRange(copy);
149+
buffer.Distinct().Count().Should().Be(cap);
150+
151+
sut.ReturnAll(buffer).Should().BeTrue();
152+
buffer.Count.Should().Be(0);
153+
sut.Count.Should().Be(cap);
154+
}
155+
156+
[Fact]
157+
public void ShouldReturnAndKeepNonReturnedList()
158+
{
159+
const int cap = 5;
160+
var sut = ObjectPool.Create<TestObj>(cap);
161+
List<TestObj> buffer = new(cap + 1);
162+
sut.Rent(buffer, buffer.Capacity);
163+
sut.Count.Should().Be(0);
164+
165+
sut.ReturnAll(buffer).Should().BeFalse();
166+
buffer.Count.Should().Be(1);
167+
sut.Count.Should().Be(cap);
168+
}
169+
109170
static void FillPool(ObjectPool<TestObj> sut, int count) =>
110171
Enumerable.Repeat<object?>(null, count)
111172
.Select(_ => sut.Rent())
112173
.ToList()
113174
.ForEach(x => sut.Return(x));
114175

115-
[Serializable]
116176
sealed class TestObj
117177
{
118-
public int Value { get; set; }
178+
static int index;
179+
public int Value { get; set; } = Interlocked.Increment(ref index);
119180
public bool Returned { get; set; }
181+
182+
public override string ToString() => $"{(Returned ? "###" : "")}Obj[{Value}]";
120183
}
121184

122185
sealed class TestObjDisposable : IDisposable

tests/Backdash.Tests/Specs/Unit/Serialization/BinaryBufferReadWriteListTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,52 @@ public T Rent()
540540
return new();
541541
}
542542

543+
public void Rent(ICollection<T> values, int count)
544+
{
545+
for (var i = 0; i < count; i++)
546+
values.Add(Rent());
547+
}
548+
543549
public bool Return(T value)
544550
{
545551
returned.Add(value);
546552
return true;
547553
}
554+
555+
public bool Return(ref T? value)
556+
{
557+
if (value is null || !Return(value)) return false;
558+
value = default;
559+
return true;
560+
}
561+
562+
public bool ReturnAll(List<T> list)
563+
{
564+
var result = true;
565+
List<int> removeIdx = [];
566+
567+
for (var i = 0; i < list.Count; i++)
568+
{
569+
var current = list[i];
570+
if (Return(current))
571+
removeIdx.Add(i);
572+
else
573+
result = false;
574+
}
575+
576+
foreach (var idx in removeIdx)
577+
list.RemoveAt(idx);
578+
579+
return result;
580+
}
581+
582+
public bool ReturnMany(IEnumerable<T> values) =>
583+
values.Aggregate(true, (result, current) =>
584+
{
585+
var ret = Return(current);
586+
return result && ret;
587+
});
588+
589+
public bool ReturnMany(ReadOnlySpan<T> values) => ReturnMany(values.ToArray().AsEnumerable());
548590
}
549591
}

0 commit comments

Comments
 (0)