-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
namespace Chickensoft.Collections.Tests; | ||
|
||
using System; | ||
using Shouldly; | ||
using Xunit; | ||
|
||
public class PoolTest { | ||
public abstract class Shape : IPooled { | ||
public void Reset() { } | ||
} | ||
|
||
public class Cube : Shape { } | ||
public class Sphere : Shape { } | ||
|
||
[Fact] | ||
public void InitializesAndRegisters() { | ||
var pool = new Pool<Shape>(); | ||
|
||
pool.Register<Cube>(); | ||
pool.Register<Sphere>(); | ||
|
||
pool.Get<Cube>().ShouldBeOfType<Cube>(); | ||
pool.Get<Sphere>().ShouldBeOfType<Sphere>(); | ||
} | ||
|
||
[Fact] | ||
public void ThrowsIfRegisteringDuplicateType() { | ||
var pool = new Pool<Shape>(); | ||
|
||
pool.Register<Cube>(); | ||
|
||
Should.Throw<InvalidOperationException>(() => pool.Register<Cube>(1)); | ||
} | ||
|
||
[Fact] | ||
public void ThrowsIfGettingUnregisteredType() { | ||
var pool = new Pool<Shape>(); | ||
|
||
Should.Throw<InvalidOperationException>(pool.Get<Cube>); | ||
} | ||
|
||
[Fact] | ||
public void CreatesNewInstancesIfPoolIsEmpty() { | ||
var pool = new Pool<Shape>(); | ||
|
||
pool.Register<Cube>(); | ||
|
||
var cube1 = pool.Get<Cube>(); | ||
var cube2 = pool.Get<Cube>(); | ||
|
||
cube1.ShouldNotBeSameAs(cube2); | ||
} | ||
|
||
[Fact] | ||
public void ReturnThrowsIfTypeNotRegistered() { | ||
var pool = new Pool<Shape>(); | ||
|
||
Should.Throw<InvalidOperationException>(() => pool.Return(new Cube())); | ||
} | ||
|
||
[Fact] | ||
public void ReturnResetsAndEnqueues() { | ||
var pool = new Pool<Shape>(); | ||
|
||
pool.Register<Cube>(); | ||
|
||
var cube = pool.Get<Cube>(); | ||
|
||
pool.Return(cube); | ||
|
||
var cube2 = pool.Get<Cube>(); | ||
|
||
cube.ShouldBeSameAs(cube2); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace Chickensoft.Collections; | ||
|
||
/// <summary> | ||
/// Represents a type that can be stored in a pool. Pools maintain | ||
/// pre-instantiated instances that can be borrowed and returned to avoid | ||
/// excess memory allocations. Pooled objects are "reset" whenever they are | ||
/// returned to the pool so that they can be reused. | ||
/// </summary> | ||
public interface IPooled { | ||
/// <summary>Resets the object to its default state.</summary> | ||
void Reset(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
namespace Chickensoft.Collections.Tests; | ||
|
||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
|
||
/// <summary> | ||
/// Represents a pool of objects that can be borrowed and returned. Objects | ||
/// are pre-instantiated and stored in the pool. When an object is borrowed, | ||
/// it is removed from the pool. When an object is returned, it is reset and | ||
/// placed back into the pool. | ||
/// </summary> | ||
public class Pool<TBaseType> where TBaseType : IPooled { | ||
private readonly ConcurrentDictionary<Type, ConcurrentQueue<TBaseType>> | ||
_pool = []; | ||
private readonly Dictionary<Type, Func<TBaseType>> _factories = []; | ||
|
||
/// <summary>Registers a type with the pool.</summary> | ||
/// <param name="capacity">The number of items to instantiate for each type | ||
/// registered in the pool.</param> | ||
/// <typeparam name="TDerivedType">The type to register.</typeparam> | ||
public void Register<TDerivedType>(int capacity = 1) | ||
where TDerivedType : TBaseType, new() => Register(() => new TDerivedType()); | ||
|
||
/// <summary>Registers a type with the pool.</summary> | ||
/// <param name="factory">A factory function that creates an instance of the | ||
/// type to register.</param> | ||
/// <param name="capacity">The number of items to instantiate for each type | ||
/// registered in the pool.</param> | ||
/// <typeparam name="TDerivedType">The type to register.</typeparam> | ||
/// <exception cref="InvalidOperationException" /> | ||
public void Register<TDerivedType>( | ||
Func<TDerivedType> factory, int capacity = 1 | ||
) | ||
where TDerivedType : TBaseType { | ||
var type = typeof(TDerivedType); | ||
var queue = new ConcurrentQueue<TBaseType>(); | ||
|
||
for (var i = 0; i < capacity; i++) { | ||
var item = factory(); | ||
queue.Enqueue(item); | ||
} | ||
|
||
lock (_pool) { | ||
if (!_pool.TryAdd(type, queue)) { | ||
throw new InvalidOperationException( | ||
$"Type `{type}` is already registered." | ||
); | ||
} | ||
|
||
_factories.TryAdd(type, () => factory()); | ||
} | ||
} | ||
|
||
/// <summary>Borrows an object from the pool.</summary> | ||
/// <typeparam name="TDerivedType">The type of object to borrow.</typeparam> | ||
/// <returns>An object of the specified type.</returns> | ||
public TDerivedType Get<TDerivedType>() where TDerivedType : TBaseType, new() | ||
=> (TDerivedType)Get(typeof(TDerivedType)); | ||
|
||
/// <summary>Borrows an object from the pool.</summary> | ||
/// <param name="type">The type of object to borrow.</param> | ||
/// <returns>An object of the specified type.</returns> | ||
public TBaseType Get(Type type) { | ||
if (!_pool.TryGetValue(type, out var queue)) { | ||
throw new InvalidOperationException($"Type `{type}` is not registered."); | ||
} | ||
|
||
if (queue.TryDequeue(out var item)) { | ||
return item; | ||
} | ||
|
||
// Out of values. Just make one. | ||
return _factories[type](); | ||
} | ||
|
||
/// <summary>Returns an object to the pool.</summary> | ||
/// <param name="item">The object to return.</param> | ||
/// <exception cref="InvalidOperationException" /> | ||
public void Return(TBaseType item) { | ||
var type = item.GetType(); | ||
|
||
if (!_pool.TryGetValue(type, out var queue)) { | ||
throw new InvalidOperationException($"Type `{type}` is not registered."); | ||
} | ||
|
||
item.Reset(); | ||
|
||
queue.Enqueue(item); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters