Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.2.0] - 2020-01-19

- Added new *ObjectPool* & *GameObjectPool* pools to allow to allow to use object pools independent from the *PoolService*. This allows to have different pools of the same type in the project in different object controllers
- Added new interface *IPoolEntityClear* that allows a callback method for entities when they are cleared from the pool
- Added new unit tests for the *ObjectPool*

**Changed**:
- Now the PoolService.Clear() does not take any action parameters. To have a callback when the entity is cleared, please have the entity implement the *IPoolEntityClear* interface

## [0.1.1] - 2020-01-06

- Added License
Expand Down
285 changes: 171 additions & 114 deletions Runtime/PoolService.cs
Original file line number Diff line number Diff line change
@@ -1,48 +1,29 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Object = UnityEngine.Object;

// ReSharper disable once CheckNamespace

namespace GameLovers.Services
{
/// <summary>
/// This interface allows pooled objects to be notified when it is spawned
/// </summary>
public interface IPoolEntitySpawn
{
/// <summary>
/// Invoked when the Entity is spawned
/// </summary>
void OnSpawn();
}

/// <summary>
/// This interface allows pooled objects to be notified when it is despawned
/// </summary>
public interface IPoolEntityDespawn
{
/// <summary>
/// Invoked when the entity is despawned
/// </summary>
void OnDespawn();
}

/// <summary>
/// Simple pool implementation that can handle any type of entity objects
/// This service allows to manage multiple pools of different types.
/// The service can only a single pool of the same type.
/// </summary>
public interface IPoolService
{
/// <summary>
/// Initializes a new pool with the given <paramref name="initialSize"/>
/// It invokes the <paramref name="instantiator"/> function every time a new entity is created in the pool
/// </summary>
void InitPool<T>(int initialSize, Func<T> instantiator);
void InitPool<T>(int initialSize, Func<T> instantiator) where T : new();

/// <summary>
/// Initializes a new pool with the given <paramref name="initialSize"/> and a sample entity given back in the <paramref name="instantiator"/>
/// It invokes the <paramref name="instantiator"/> function every time a new entity is created in the pool
/// </summary>
void InitPool<T>(int initialSize, T sampleEntity, Func<T, T> instantiator);
void InitPool<T>(int initialSize, T sampleEntity, Func<T, T> instantiator) where T : Object;

/// <summary>
/// Checks if exists a pool of the given type already exists or needs to be initialized with
Expand All @@ -53,65 +34,34 @@ public interface IPoolService
/// <inheritdoc cref="HasPool{T}"/>
bool HasPool(Type type);

/// <summary>
/// Spawns and returns an entity of the given type <typeparamref name="T"/>
/// This function does not initialize the entity. For that, have the entity implement <see cref="IPoolEntitySpawn"/> or do it externally
/// This function throws a <exception cref="StackOverflowException" /> if the pool is empty
/// </summary>
/// <inheritdoc cref="IObjectPool{T}.Spawn"/>
T Spawn<T>();

/// <summary>
/// Despawns the given <paramref name="entity"/> and returns it back to the pool to be used again later
/// This function does not reset the entity. For that, have the entity implement <see cref="IPoolEntityDespawn"/> or do it externally
/// </summary>
/// <inheritdoc cref="IObjectPool{T}.Despawn"/>
void Despawn<T>(T entity);

/// <summary>
/// Despawns all active spawned entities of the given type <typeparamref name="T"/> and returns them back to the pool to be used again later
/// This function does not reset the entity. For that, have the entity implement <see cref="IPoolEntityDespawn"/> or do it externally
/// </summary>
/// <inheritdoc cref="IObjectPool{T}.DespawnAll"/>
void DespawnAll<T>();

/// <summary>
/// Clears the pool of the given type <typeparamref name="T"/>
/// It calls <paramref name="clearAction"/> with every entity remaining in the pool and managed by the pool
/// </summary>
void Clear<T>(Action<T> clearAction);
/// <inheritdoc cref="IObjectPool{T}.Clear"/>
void Clear<T>();
}

/// <inheritdoc />
public class PoolService : IPoolService
{
private readonly Dictionary<Type, IPoolStack> pools = new Dictionary<Type, IPoolStack>();
private readonly Dictionary<Type, IObjectPool> _pools = new Dictionary<Type, IObjectPool>();

/// <inheritdoc />
public void InitPool<T>(int initialSize, Func<T> instantiator)
public void InitPool<T>(int initialSize, Func<T> instantiator) where T : new()
{
InitPool(initialSize, instantiator.Invoke(), newEntity => instantiator.Invoke());
_pools.Add(typeof(T), new ObjectPool<T>(initialSize, instantiator));
}

/// <inheritdoc />
public void InitPool<T>(int initialSize, T sampleEntity, Func<T, T> instantiator)
public void InitPool<T>(int initialSize, T sampleEntity, Func<T, T> instantiator) where T : Object
{
if (pools.ContainsKey(typeof(T)))
{
throw new InvalidOperationException($"The pool of type {typeof(T)} was already initialized");
}

var pool = new PoolStack<T>
{
Stack = new Stack<T>(),
SpawnedEntities = new List<T>(),
SampleEntity = sampleEntity,
Instatiator = instantiator
};

pools.Add(typeof(T), pool);

for (int i = 0; i < initialSize; i++)
{
pool.Stack.Push(instantiator.Invoke(sampleEntity));
}
_pools.Add(typeof(T), new GameObjectPool<T>(initialSize, sampleEntity, instantiator));
}

/// <inheritdoc />
Expand All @@ -123,88 +73,195 @@ public bool HasPool<T>()
/// <inheritdoc />
public bool HasPool(Type type)
{
return pools.ContainsKey(type);
return _pools.ContainsKey(type);
}

/// <inheritdoc />
public T Spawn<T>()
{
var pool = GetPool<T>();
T entity = pool.Stack.Count == 0 ? pool.Instatiator.Invoke(pool.SampleEntity) : pool.Stack.Pop();
var poolEntity = entity as IPoolEntitySpawn;

pool.SpawnedEntities.Add(entity);
poolEntity?.OnSpawn();

return entity;
return GetPool<T>().Spawn();
}

/// <inheritdoc />
public void Despawn<T>(T entity)
{
var pool = GetPool<T>();
var poolEntity = entity as IPoolEntityDespawn;

pool.Stack.Push(entity);
pool.SpawnedEntities.Remove(entity);
poolEntity?.OnDespawn();
GetPool<T>().Despawn(entity);
}

/// <inheritdoc />
public void DespawnAll<T>()
{
var pool = GetPool<T>();

foreach (T entity in pool.SpawnedEntities)
{
var poolEntity = entity as IPoolEntityDespawn;
GetPool<T>().DespawnAll();
}

pool.Stack.Push(entity);
poolEntity?.OnDespawn();
/// <inheritdoc />
public void Clear<T>()
{
GetPool<T>().Clear();
_pools.Remove(typeof(T));
}

private IObjectPool<T> GetPool<T>()
{
if (!_pools.TryGetValue(typeof(T), out IObjectPool pool))
{
throw new ArgumentException("The pool was not initialized for the type " + typeof(T));
}
pool.SpawnedEntities.Clear();

return pool as IObjectPool<T>;
}
}

/// <summary>
/// This interface allows pooled objects to be notified when it is spawned
/// </summary>
public interface IPoolEntitySpawn
{
/// <summary>
/// Invoked when the Entity is spawned
/// </summary>
void OnSpawn();
}

/// <summary>
/// This interface allows pooled objects to be notified when it is despawned
/// </summary>
public interface IPoolEntityDespawn
{
/// <summary>
/// Invoked when the entity is despawned
/// </summary>
void OnDespawn();
}

/// <summary>
/// This interface allows pooled objects to be notified when they are cleared from the pool
/// </summary>
public interface IPoolEntityCleared
{
/// <summary>
/// Invoked when the entity is cleared
/// </summary>
void OnCleared();
}

/// <summary>
/// Simple object pool implementation that can handle any type of entity objects
/// </summary>
public interface IObjectPool
{
/// <summary>
/// Clears the pool
/// This function does not clear the entity. For that, have the entity implement <see cref="IPoolEntityCleared"/> or do it externally
/// </summary>
void Clear();

/// <summary>
/// Despawns all active spawned entities and returns them back to the pool to be used again later
/// This function does not reset the entity. For that, have the entity implement <see cref="IPoolEntityDespawn"/> or do it externally
/// </summary>
void DespawnAll();
}

/// <inheritdoc />
public interface IObjectPool<T> : IObjectPool
{
/// <summary>
/// Spawns and returns an entity of the given type <typeparamref name="T"/>
/// This function does not initialize the entity. For that, have the entity implement <see cref="IPoolEntitySpawn"/> or do it externally
/// This function throws a <exception cref="StackOverflowException" /> if the pool is empty
/// </summary>
T Spawn();

/// <summary>
/// Despawns the given <paramref name="entity"/> and returns it back to the pool to be used again later
/// This function does not reset the entity. For that, have the entity implement <see cref="IPoolEntityDespawn"/> or do it externally
/// </summary>
void Despawn(T entity);
}

/// <inheritdoc />
public abstract class ObjectPoolBase<T> : IObjectPool<T>
{
private readonly Stack<T> _stack = new Stack<T>();
private readonly IList<T> _spawnedEntities = new List<T>();
private readonly Func<T, T> _instantiator;
private readonly T _sampleEntity;

protected ObjectPoolBase(int initSize, T sampleEntity, Func<T, T> instantiator)
{
_sampleEntity = sampleEntity;
_instantiator = instantiator;

for (var i = 0; i < initSize; i++)
{
_stack.Push(instantiator.Invoke(sampleEntity));
}
}

/// <inheritdoc />
public void Clear<T>(Action<T> clearAction)
public void Clear()
{
var pool = GetPool<T>();

for (var i = 0; i < pool.Stack.Count; i++)
for (var i = 0; i < _stack.Count; i++)
{
T entity = pool.Stack.Pop();
var entity =_stack.Pop() as IPoolEntityCleared;

clearAction?.Invoke(entity);
entity?.OnCleared();
}

pool.SpawnedEntities.Clear();
pools.Remove(typeof(T));
_spawnedEntities.Clear();
}

private PoolStack<T> GetPool<T>()
/// <inheritdoc />
public T Spawn()
{
if (!pools.TryGetValue(typeof(T), out IPoolStack poolStack))
{
throw new ArgumentException("The pool was not initialized for the type " + typeof(T));
}
var entity = _stack.Count == 0 ? _instantiator.Invoke(_sampleEntity) : _stack.Pop();
var poolEntity = entity as IPoolEntitySpawn;

_spawnedEntities.Add(entity);
poolEntity?.OnSpawn();

return entity;
}

/// <inheritdoc />
public void Despawn(T entity)
{
var poolEntity = entity as IPoolEntityDespawn;

_stack.Push(entity);
_spawnedEntities.Remove(entity);
poolEntity?.OnDespawn();
}

if (poolStack is PoolStack<T> pool)
/// <inheritdoc />
public void DespawnAll()
{
for (var i = 0; i < _spawnedEntities.Count; i++)
{
return pool;
Despawn(_spawnedEntities[i]);
}
throw new ArgumentException("The pool was not properly initialized for the type " + typeof(T));

_spawnedEntities.Clear();
}

private interface IPoolStack {}
}

/// <inheritdoc />
public class ObjectPool<T> : ObjectPoolBase<T> where T : new()
{
public ObjectPool(int initSize, Func<T> instantiator) : base(initSize, instantiator(), newEntity => instantiator.Invoke())
{
}
}

private class PoolStack<T> : IPoolStack
/// <inheritdoc />
/// <remarks>
/// <see cref="IObjectPool"/> implementation for objects of type <see cref="Object"/>
/// </remarks>
public class GameObjectPool<T> : ObjectPoolBase<T> where T : Object
{
public GameObjectPool(int initSize, T sampleEntity, Func<T, T> instantiator) : base(initSize, sampleEntity, instantiator)
{
public Stack<T> Stack;
public List<T> SpawnedEntities;
public Func<T, T> Instatiator;
public T SampleEntity;
}
}
}
Loading