diff --git a/CHANGELOG.md b/CHANGELOG.md
index 931ecaa..d288f4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/Runtime/PoolService.cs b/Runtime/PoolService.cs
index dbafc63..bea7473 100644
--- a/Runtime/PoolService.cs
+++ b/Runtime/PoolService.cs
@@ -1,34 +1,15 @@
using System;
using System.Collections.Generic;
+using UnityEngine;
+using Object = UnityEngine.Object;
// ReSharper disable once CheckNamespace
namespace GameLovers.Services
{
///
- /// This interface allows pooled objects to be notified when it is spawned
- ///
- public interface IPoolEntitySpawn
- {
- ///
- /// Invoked when the Entity is spawned
- ///
- void OnSpawn();
- }
-
- ///
- /// This interface allows pooled objects to be notified when it is despawned
- ///
- public interface IPoolEntityDespawn
- {
- ///
- /// Invoked when the entity is despawned
- ///
- void OnDespawn();
- }
-
- ///
- /// 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.
///
public interface IPoolService
{
@@ -36,13 +17,13 @@ public interface IPoolService
/// Initializes a new pool with the given
/// It invokes the function every time a new entity is created in the pool
///
- void InitPool(int initialSize, Func instantiator);
+ void InitPool(int initialSize, Func instantiator) where T : new();
///
/// Initializes a new pool with the given and a sample entity given back in the
/// It invokes the function every time a new entity is created in the pool
///
- void InitPool(int initialSize, T sampleEntity, Func instantiator);
+ void InitPool(int initialSize, T sampleEntity, Func instantiator) where T : Object;
///
/// Checks if exists a pool of the given type already exists or needs to be initialized with
@@ -53,65 +34,34 @@ public interface IPoolService
///
bool HasPool(Type type);
- ///
- /// Spawns and returns an entity of the given type
- /// This function does not initialize the entity. For that, have the entity implement or do it externally
- /// This function throws a if the pool is empty
- ///
+ ///
T Spawn();
- ///
- /// Despawns the given 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 or do it externally
- ///
+ ///
void Despawn(T entity);
- ///
- /// Despawns all active spawned entities of the given type 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 or do it externally
- ///
+ ///
void DespawnAll();
- ///
- /// Clears the pool of the given type
- /// It calls with every entity remaining in the pool and managed by the pool
- ///
- void Clear(Action clearAction);
+ ///
+ void Clear();
}
///
public class PoolService : IPoolService
{
- private readonly Dictionary pools = new Dictionary();
+ private readonly Dictionary _pools = new Dictionary();
///
- public void InitPool(int initialSize, Func instantiator)
+ public void InitPool(int initialSize, Func instantiator) where T : new()
{
- InitPool(initialSize, instantiator.Invoke(), newEntity => instantiator.Invoke());
+ _pools.Add(typeof(T), new ObjectPool(initialSize, instantiator));
}
///
- public void InitPool(int initialSize, T sampleEntity, Func instantiator)
+ public void InitPool(int initialSize, T sampleEntity, Func 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
- {
- Stack = new Stack(),
- SpawnedEntities = new List(),
- 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(initialSize, sampleEntity, instantiator));
}
///
@@ -123,88 +73,195 @@ public bool HasPool()
///
public bool HasPool(Type type)
{
- return pools.ContainsKey(type);
+ return _pools.ContainsKey(type);
}
///
public T Spawn()
{
- var pool = GetPool();
- 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().Spawn();
}
///
public void Despawn(T entity)
{
- var pool = GetPool();
- var poolEntity = entity as IPoolEntityDespawn;
-
- pool.Stack.Push(entity);
- pool.SpawnedEntities.Remove(entity);
- poolEntity?.OnDespawn();
+ GetPool().Despawn(entity);
}
///
public void DespawnAll()
{
- var pool = GetPool();
-
- foreach (T entity in pool.SpawnedEntities)
- {
- var poolEntity = entity as IPoolEntityDespawn;
+ GetPool().DespawnAll();
+ }
- pool.Stack.Push(entity);
- poolEntity?.OnDespawn();
+ ///
+ public void Clear()
+ {
+ GetPool().Clear();
+ _pools.Remove(typeof(T));
+ }
+
+ private IObjectPool GetPool()
+ {
+ 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;
}
+ }
+
+ ///
+ /// This interface allows pooled objects to be notified when it is spawned
+ ///
+ public interface IPoolEntitySpawn
+ {
+ ///
+ /// Invoked when the Entity is spawned
+ ///
+ void OnSpawn();
+ }
+
+ ///
+ /// This interface allows pooled objects to be notified when it is despawned
+ ///
+ public interface IPoolEntityDespawn
+ {
+ ///
+ /// Invoked when the entity is despawned
+ ///
+ void OnDespawn();
+ }
+
+ ///
+ /// This interface allows pooled objects to be notified when they are cleared from the pool
+ ///
+ public interface IPoolEntityCleared
+ {
+ ///
+ /// Invoked when the entity is cleared
+ ///
+ void OnCleared();
+ }
+
+ ///
+ /// Simple object pool implementation that can handle any type of entity objects
+ ///
+ public interface IObjectPool
+ {
+ ///
+ /// Clears the pool
+ /// This function does not clear the entity. For that, have the entity implement or do it externally
+ ///
+ void Clear();
+
+ ///
+ /// 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 or do it externally
+ ///
+ void DespawnAll();
+ }
+
+ ///
+ public interface IObjectPool : IObjectPool
+ {
+ ///
+ /// Spawns and returns an entity of the given type
+ /// This function does not initialize the entity. For that, have the entity implement or do it externally
+ /// This function throws a if the pool is empty
+ ///
+ T Spawn();
+
+ ///
+ /// Despawns the given 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 or do it externally
+ ///
+ void Despawn(T entity);
+ }
+ ///
+ public abstract class ObjectPoolBase : IObjectPool
+ {
+ private readonly Stack _stack = new Stack();
+ private readonly IList _spawnedEntities = new List();
+ private readonly Func _instantiator;
+ private readonly T _sampleEntity;
+
+ protected ObjectPoolBase(int initSize, T sampleEntity, Func instantiator)
+ {
+ _sampleEntity = sampleEntity;
+ _instantiator = instantiator;
+
+ for (var i = 0; i < initSize; i++)
+ {
+ _stack.Push(instantiator.Invoke(sampleEntity));
+ }
+ }
+
///
- public void Clear(Action clearAction)
+ public void Clear()
{
- var pool = GetPool();
-
- 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 GetPool()
+ ///
+ 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;
+ }
+
+ ///
+ public void Despawn(T entity)
+ {
+ var poolEntity = entity as IPoolEntityDespawn;
+
+ _stack.Push(entity);
+ _spawnedEntities.Remove(entity);
+ poolEntity?.OnDespawn();
+ }
- if (poolStack is PoolStack pool)
+ ///
+ 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 {}
+ }
+
+ ///
+ public class ObjectPool : ObjectPoolBase where T : new()
+ {
+ public ObjectPool(int initSize, Func instantiator) : base(initSize, instantiator(), newEntity => instantiator.Invoke())
+ {
+ }
+ }
- private class PoolStack : IPoolStack
+ ///
+ ///
+ /// implementation for objects of type
+ ///
+ public class GameObjectPool : ObjectPoolBase where T : Object
+ {
+ public GameObjectPool(int initSize, T sampleEntity, Func instantiator) : base(initSize, sampleEntity, instantiator)
{
- public Stack Stack;
- public List SpawnedEntities;
- public Func Instatiator;
- public T SampleEntity;
}
}
}
\ No newline at end of file
diff --git a/Tests/Editor/MainInstallerTest.cs b/Tests/Editor/MainInstallerTest.cs
index 6ad523e..dfc6778 100644
--- a/Tests/Editor/MainInstallerTest.cs
+++ b/Tests/Editor/MainInstallerTest.cs
@@ -44,7 +44,7 @@ public void Bind_NotImplementing_ThrowsException()
[Test]
public void Resolve_NotBinded_ThrowsException()
{
- Assert.Throws(() => MainInstaller.Resolve());
+ Assert.Throws(() => MainInstaller.Resolve());
}
}
}
\ No newline at end of file
diff --git a/Tests/Editor/ObjectPoolTest.cs b/Tests/Editor/ObjectPoolTest.cs
new file mode 100644
index 0000000..3a4e0dc
--- /dev/null
+++ b/Tests/Editor/ObjectPoolTest.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using GameLovers.Services;
+using NSubstitute;
+using NUnit.Framework;
+
+// ReSharper disable once CheckNamespace
+
+namespace GameLoversEditor.Services.Tests
+{
+ public class ObjectPoolTest
+ {
+ private ObjectPool _pool;
+ private PoolableEntity _poolableEntity;
+ private int initialSize = 5;
+
+ public class PoolableEntity : IPoolEntitySpawn, IPoolEntityDespawn, IPoolEntityCleared
+ {
+ public void OnSpawn() {}
+ public void OnDespawn() {}
+ public void OnCleared() {}
+ }
+
+ [SetUp]
+ public void Init()
+ {
+ _pool = new ObjectPool(initialSize, () => Substitute.For());
+ _poolableEntity = Substitute.For();
+ }
+
+ [Test]
+ public void Spawn_Successfully()
+ {
+ var newEntity = _pool.Spawn();
+
+ newEntity.Received().OnSpawn();
+
+ Assert.AreNotSame(_poolableEntity, newEntity);
+ }
+
+ [Test]
+ public void Spawn_EmptyPool_Successfully()
+ {
+ var pool = new ObjectPool(initialSize, () => Substitute.For());
+
+ var newEntity = pool.Spawn();
+
+ newEntity.Received().OnSpawn();
+
+ Assert.AreNotSame(_poolableEntity, newEntity);
+ }
+
+ [Test]
+ public void Despawn_Successfully()
+ {
+ _pool.Despawn(_poolableEntity);
+
+ _poolableEntity.Received().OnDespawn();
+ }
+
+ [Test]
+ public void DespawnAll_Successfully()
+ {
+ var entities = new List();
+
+ for (int i = 0; i < initialSize; i++)
+ {
+ entities.Add(Substitute.For());
+ }
+
+ _pool.DespawnAll();
+
+ foreach (var entity in entities)
+ {
+ entity.Received().OnDespawn();
+ }
+ }
+
+ [Test]
+ public void Clear_Successfully()
+ {
+ _pool.Despawn(_poolableEntity);
+ _pool.Clear();
+
+ _poolableEntity.Received().OnCleared();
+
+ Assert.DoesNotThrow(() => _pool.Spawn());
+ Assert.DoesNotThrow(() => _pool.Despawn(_poolableEntity));
+ }
+
+ [Test]
+ public void Clear_Twice_NothingHappens()
+ {
+ _pool.Clear();
+
+ Assert.DoesNotThrow(() => _pool.Clear());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/Editor/ObjectPoolTest.cs.meta b/Tests/Editor/ObjectPoolTest.cs.meta
new file mode 100644
index 0000000..4fa9628
--- /dev/null
+++ b/Tests/Editor/ObjectPoolTest.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e5572d97c03ad4be8a5d0a16c3b9655f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Tests/Editor/PoolServiceTest.cs b/Tests/Editor/PoolServiceTest.cs
index 72ef6c2..08ef23d 100644
--- a/Tests/Editor/PoolServiceTest.cs
+++ b/Tests/Editor/PoolServiceTest.cs
@@ -12,11 +12,13 @@ public class PoolServiceTest
{
private PoolService _poolService;
private PoolableEntity _poolableEntity;
+ private int initialSize = 5;
- public abstract class PoolableEntity : IPoolEntitySpawn, IPoolEntityDespawn
+ public class PoolableEntity : IPoolEntitySpawn, IPoolEntityDespawn, IPoolEntityCleared
{
- public abstract void OnSpawn();
- public abstract void OnDespawn();
+ public void OnSpawn() {}
+ public void OnDespawn() {}
+ public void OnCleared() {}
}
[SetUp]
@@ -24,28 +26,22 @@ public void Init()
{
_poolService = new PoolService();
_poolableEntity = Substitute.For();
+
+ _poolService.InitPool(initialSize, () => Substitute.For());
}
[Test]
public void Initialize_SameType_ThrowsException()
{
- var initialSize = 5;
-
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
-
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
+ _poolService.InitPool(initialSize, () => Substitute.For());
});
}
[Test]
public void Spawn_Successfully()
{
- var initialSize = 5;
-
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
-
var newEntity = _poolService.Spawn();
newEntity.Received().OnSpawn();
@@ -57,8 +53,9 @@ public void Spawn_Successfully()
public void Spawn_EmptyPool_Successfully()
{
var initialSize = 0;
+ var poolService = new PoolService();
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
+ poolService.InitPool(initialSize, () => Substitute.For());
var newEntity = _poolService.Spawn();
@@ -70,15 +67,14 @@ public void Spawn_EmptyPool_Successfully()
[Test]
public void Spawn_NotInitialized_ThrowsException()
{
- Assert.Throws(() => _poolService.Spawn());
+ var poolService = new PoolService();
+
+ Assert.Throws(() => poolService.Spawn());
}
[Test]
public void Despawn_Successfully()
{
- var initialSize = 5;
-
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
_poolService.Despawn(_poolableEntity);
_poolableEntity.Received().OnDespawn();
@@ -87,20 +83,19 @@ public void Despawn_Successfully()
[Test]
public void Despawn_NotInitialized_ThrowsException()
{
- Assert.Throws(() => _poolService.Despawn(_poolableEntity));
+ var poolService = new PoolService();
+
+ Assert.Throws(() => poolService.Despawn(_poolableEntity));
}
[Test]
public void DespawnAll_Successfully()
{
- var initialSize = 5;
var entities = new List();
-
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
for (int i = 0; i < initialSize; i++)
{
- entities.Add(_poolService.Spawn());
+ entities.Add(Substitute.For());
}
_poolService.DespawnAll();
@@ -114,10 +109,10 @@ public void DespawnAll_Successfully()
[Test]
public void Clear_Successfully()
{
- var initialSize = 5;
+ _poolService.Despawn(_poolableEntity);
+ _poolService.Clear();
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
- _poolService.Clear(poolableEntity => { });
+ _poolableEntity.Received().OnCleared();
Assert.Throws(() => _poolService.Spawn());
Assert.Throws(() => _poolService.Despawn(_poolableEntity));
@@ -126,18 +121,17 @@ public void Clear_Successfully()
[Test]
public void Clear_NotInitialized_ThrowsException()
{
- Assert.Throws(() => _poolService.Clear(null));
+ var poolService = new PoolService();
+
+ Assert.Throws(() => poolService.Clear());
}
[Test]
public void Clear_SameType_ThrowsException()
{
- var initialSize = 5;
-
- _poolService.InitPool(initialSize, _poolableEntity, poolableEntity => Substitute.For());
- _poolService.Clear(poolableEntity => { });
+ _poolService.Clear();
- Assert.Throws(() => _poolService.Clear(null));
+ Assert.Throws(() => _poolService.Clear());
}
}
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 1003beb..1a3fb98 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "com.gamelovers.services",
"displayName": "Services",
- "version": "0.1.1",
+ "version": "0.2.0",
"unity": "2019.3",
"description": "This package contains a set of services to ease the development of a basic game architecture. The services provided by this package are:\n\n- CoroutineService to control all coroutines with an end callback\n- PoolService to control all object pools by type\n- MessageBrokerService to help decoupled modules/systems to communicate with each other while maintaining the inversion of control principle\n- TimeService to have a precise control on the game's time (Unix, Unity or DateTime)\n- TickService to have a single control point on Unity update cycle\n- MainInstaller to have a simple dependency injection binding installer",
"type": "library",