From 413e40bf14eec434397a26ccbf15cb2bce5c4e2c Mon Sep 17 00:00:00 2001 From: CoderGamester Date: Sun, 5 Jan 2025 21:33:11 +0000 Subject: [PATCH] **New**: - Added *StartDelayCall* method to *ICoroutineService* to allow deferred methods to be safely executed within the bounds of a Unity Coroutine - Added the possibility to know the current state of an *IAsyncCoroutine* - Added the access to the Sample Entity used to generete new entites within an *IObjectPool* and destroy it when disposing the object pool - Added the possibility to reset an *IObjectPool* to a new state --- CHANGELOG.md | 8 ++ Runtime/CoroutineService.cs | 135 +++++++++++++----- Runtime/ObjectPool.cs | 93 +++++++++--- Runtime/PoolService.cs | 12 +- Tests/Editor/PlayMode/CoroutineServiceTest.cs | 4 +- package.json | 2 +- 6 files changed, 190 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c14482f..d3fcec4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ 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.15.0] - 2025-01-05 + +**New**: +- Added *StartDelayCall* method to *ICoroutineService* to allow deferred methods to be safely executed within the bounds of a Unity Coroutine +- Added the possibility to know the current state of an *IAsyncCoroutine* +- Added the access to the Sample Entity used to generete new entites within an *IObjectPool* and destroy it when disposing the object pool +- Added the possibility to reset an *IObjectPool* to a new state + ## [0.14.1] - 2024-11-30 **Fixed**: diff --git a/Runtime/CoroutineService.cs b/Runtime/CoroutineService.cs index 2c0ad3d..79df13b 100644 --- a/Runtime/CoroutineService.cs +++ b/Runtime/CoroutineService.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using UnityEngine; +using Action = System.Action; using Object = UnityEngine.Object; // ReSharper disable once CheckNamespace @@ -13,6 +14,10 @@ namespace GameLovers.Services /// public interface IAsyncCoroutine { + /// + /// Get the execution status of the coroutine + /// + bool IsRunning { get; } /// /// Get the complete operation status of the coroutine /// @@ -21,30 +26,34 @@ public interface IAsyncCoroutine /// Get the current coroutine being executed /// Coroutine Coroutine { get; } + /// + /// The Unity time the coroutine started + /// + float StartTime { get; } /// /// Sets the action callback to be invoked when the coroutine is completed /// void OnComplete(Action onComplete); + /// + /// Stops the execution of this coroutine + /// + void StopCoroutine(bool triggerOnComplete = false); } - /// - public interface IAsyncCoroutine + /// + public interface IAsyncCoroutine : IAsyncCoroutine { - /// - bool IsCompleted { get; } - /// - Coroutine Coroutine { get; } /// /// The data to be returned on the coroutine completion /// - T Data { get; } + T Data { get; set; } /// /// Sets the action callback to be invoked when the coroutine is completed with the - /// reference in the callback + /// reference in the callback /// - void OnComplete(T data, Action onComplete); + void OnComplete(Action onComplete); } /// @@ -70,9 +79,20 @@ public interface ICoroutineService : IDisposable IAsyncCoroutine StartAsyncCoroutine(IEnumerator routine); /// /// Follows the same principle and execution of but returns - /// a to provide a callback on complete of the coroutine with given type + /// a to provide a callback on complete of the coroutine with given /// - IAsyncCoroutine StartAsyncCoroutine(IEnumerator routine); + IAsyncCoroutine StartAsyncCoroutine(IEnumerator routine, T data); + /// + /// Executes in a with the given . + /// Useful for delay callbacks + /// + IAsyncCoroutine StartDelayCall(Action call, float delay); + /// + /// Executes in a with the given + /// and data type. + /// Useful for delay callbacks + /// + IAsyncCoroutine StartDelayCall(Action call, T data,float delay); /// void StopCoroutine(Coroutine coroutine); /// @@ -86,7 +106,7 @@ public class CoroutineService : ICoroutineService public CoroutineService() { - var gameObject = new GameObject(typeof(CoroutineServiceMonoBehaviour).Name); + var gameObject = new GameObject(nameof(CoroutineServiceMonoBehaviour)); _serviceObject = gameObject.AddComponent(); @@ -120,7 +140,7 @@ public Coroutine StartCoroutine(IEnumerator routine) /// public IAsyncCoroutine StartAsyncCoroutine(IEnumerator routine) { - var asyncCoroutine = new AsyncCoroutine(); + var asyncCoroutine = new AsyncCoroutine(this); asyncCoroutine.SetCoroutine(_serviceObject.ExternalStartCoroutine(InternalCoroutine(routine, asyncCoroutine))); @@ -128,15 +148,37 @@ public IAsyncCoroutine StartAsyncCoroutine(IEnumerator routine) } /// - public IAsyncCoroutine StartAsyncCoroutine(IEnumerator routine) + public IAsyncCoroutine StartAsyncCoroutine(IEnumerator routine, T data) { - var asyncCoroutine = new AsyncCoroutine(); + var asyncCoroutine = new AsyncCoroutine(this, data); asyncCoroutine.SetCoroutine(_serviceObject.ExternalStartCoroutine(InternalCoroutine(routine, asyncCoroutine))); return asyncCoroutine; } - + + /// + public IAsyncCoroutine StartDelayCall(Action call, float delay) + { + var asyncCoroutine = new AsyncCoroutine(this); + + asyncCoroutine.OnComplete(call); + asyncCoroutine.SetCoroutine(_serviceObject.ExternalStartCoroutine(InternalDelayCoroutine(delay, asyncCoroutine))); + + return asyncCoroutine; + } + + /// + public IAsyncCoroutine StartDelayCall(Action call, T data, float delay) + { + var asyncCoroutine = new AsyncCoroutine(this, data); + + asyncCoroutine.OnComplete(call); + asyncCoroutine.SetCoroutine(_serviceObject.ExternalStartCoroutine(InternalDelayCoroutine(delay, asyncCoroutine))); + + return asyncCoroutine; + } + /// public void StopCoroutine(Coroutine coroutine) { @@ -165,22 +207,38 @@ private static IEnumerator InternalCoroutine(IEnumerator routine, ICompleteCorou completed.Completed(); } + + private static IEnumerator InternalDelayCoroutine(float delayInSeconds, ICompleteCoroutine completed) + { + yield return new WaitForSeconds(delayInSeconds); + + completed.Completed(); + } #region Private Interfaces private interface ICompleteCoroutine { void Completed(); - - void SetCoroutine(Coroutine coroutine); } private class AsyncCoroutine : IAsyncCoroutine, ICompleteCoroutine { + private readonly ICoroutineService _coroutineService; + private Action _onComplete; + public bool IsRunning => Coroutine != null; public bool IsCompleted { get; private set; } public Coroutine Coroutine { get; private set; } + public float StartTime { get; } = Time.time; + + private AsyncCoroutine() {} + + public AsyncCoroutine(ICoroutineService coroutineService) + { + _coroutineService = coroutineService; + } public void SetCoroutine(Coroutine coroutine) { @@ -192,6 +250,13 @@ public void OnComplete(Action onComplete) _onComplete = onComplete; } + public void StopCoroutine(bool triggerOnComplete = false) + { + _coroutineService.StopCoroutine(Coroutine); + + OnCompleteTrigger(); + } + public void Completed() { if (IsCompleted) @@ -201,41 +266,35 @@ public void Completed() IsCompleted = true; Coroutine = null; - + + OnCompleteTrigger(); + } + + protected virtual void OnCompleteTrigger() + { _onComplete?.Invoke(); } } - private class AsyncCoroutine : IAsyncCoroutine, ICompleteCoroutine + private class AsyncCoroutine : AsyncCoroutine, IAsyncCoroutine { private Action _onComplete; - - public bool IsCompleted { get; private set; } - public Coroutine Coroutine { get; private set; } - public T Data { get; private set; } + + public T Data { get; set; } - public void SetCoroutine(Coroutine coroutine) + public AsyncCoroutine(ICoroutineService coroutineService, T data) : base(coroutineService) { - Coroutine = coroutine; + Data = data; } - public void OnComplete(T data, Action onComplete) + public void OnComplete(Action onComplete) { _onComplete = onComplete; - - Data = data; } - public void Completed() + protected override void OnCompleteTrigger() { - if (IsCompleted) - { - return; - } - - IsCompleted = true; - Coroutine = null; - + base.OnCompleteTrigger(); _onComplete?.Invoke(Data); } } diff --git a/Runtime/ObjectPool.cs b/Runtime/ObjectPool.cs index 44c3a8a..4adc5ad 100644 --- a/Runtime/ObjectPool.cs +++ b/Runtime/ObjectPool.cs @@ -88,11 +88,22 @@ public interface IObjectPool : IDisposable /// This function does not reset the entity. For that, have the entity implement or do it externally /// void DespawnAll(); + + /// + /// + /// Will also dispose the sample entity depending on the value of + /// + void Dispose(bool disposeSampleEntity); } /// public interface IObjectPool : IObjectPool where T : class { + /// + /// The entity reference used to create the pooled entities + /// + T SampleEntity { get; } + /// /// Requests the collection of already spawned elements as a read only list /// @@ -103,6 +114,11 @@ public interface IObjectPool : IObjectPool where T : class /// bool IsSpawned(Func conditionCheck); + /// + /// Clears any entities in the pool and resets it to the given + /// + void Reset(uint initSize, T sampleEntity); + /// /// Spawns and returns an entity of the given type /// This function does not initialize the entity. For that, have the entity implement @@ -146,19 +162,22 @@ public interface IObjectPool : IObjectPool where T : class /// public abstract class ObjectPoolBase : IObjectPool where T : class { - public readonly T SampleEntity; - protected readonly IList SpawnedEntities = new List(); private readonly Stack _stack; private readonly Func _instantiator; + + private T _sampleEntity; + + /// + public T SampleEntity => _sampleEntity; /// public IReadOnlyList SpawnedReadOnly => SpawnedEntities as IReadOnlyList; protected ObjectPoolBase(uint initSize, T sampleEntity, Func instantiator) { - SampleEntity = sampleEntity; + _sampleEntity = sampleEntity; _instantiator = instantiator; _stack = new Stack((int)initSize); @@ -182,6 +201,19 @@ public bool IsSpawned(Func conditionCheck) return false; } + /// + public void Reset(uint initSize, T sampleEntity) + { + Dispose(); + + _sampleEntity = sampleEntity; + + for (var i = 0; i < initSize; i++) + { + _stack.Push(CallInstantiator()); + } + } + /// public List Clear() { @@ -203,6 +235,16 @@ public void DespawnAll() } } + public virtual void Dispose(bool disposeSampleEntity) + { + if (disposeSampleEntity) + { + _sampleEntity = null; + } + + Dispose(); + } + /// public T Spawn() { @@ -262,7 +304,11 @@ public bool Despawn(bool onlyFirst, Func entityGetter) return despawned; } - public abstract void Dispose(); + /// + public virtual void Dispose() + { + Clear(); + } protected virtual T SpawnEntity() { @@ -316,25 +362,12 @@ protected void CallOnDespawned(T entity) } /// - /// - /// Useful to for pools that use object references to create new instances (ex: GameObjects) - /// - public class ObjectRefPool : ObjectPoolBase where T : class - { - public ObjectRefPool(uint initSize, T sampleEntity, Func instantiator) : base(initSize, sampleEntity, instantiator) - { - } - - /// - public override void Dispose() - { - Clear(); - } - } - - /// - public class ObjectPool : ObjectRefPool where T : class + public class ObjectPool : ObjectPoolBase where T : class { + public ObjectPool(uint initSize, T sampleEntity, Func instantiator) : base(initSize, sampleEntity, instantiator) + { + } + public ObjectPool(uint initSize, Func instantiator) : base(initSize, instantiator(), entityRef => instantiator.Invoke()) { } @@ -360,6 +393,14 @@ public GameObjectPool(uint initSize, GameObject sampleEntity, Func + public override void Dispose(bool disposeSampleEntity) + { + Object.Destroy(SampleEntity); + + base.Dispose(disposeSampleEntity); + } + /// public override void Dispose() { @@ -423,6 +464,14 @@ public GameObjectPool(uint initSize, T sampleEntity, Func instantiator) : { } + /// + public override void Dispose(bool disposeSampleEntity) + { + Object.Destroy(SampleEntity.gameObject); + + base.Dispose(disposeSampleEntity); + } + /// public override void Dispose() { diff --git a/Runtime/PoolService.cs b/Runtime/PoolService.cs index b889e54..3e5f512 100644 --- a/Runtime/PoolService.cs +++ b/Runtime/PoolService.cs @@ -50,7 +50,7 @@ public interface IPoolService : IDisposable /// T Spawn(TData data) where T : class, IPoolEntitySpawn; - /// + /// bool Despawn(T entity) where T : class; /// @@ -70,6 +70,9 @@ public interface IPoolService : IDisposable /// A dictionary containing all the pools in this service, where the key is the type of the pool and the value is the pool itself. /// IDictionary Clear(); + + /// + void Dispose(bool disposeSampleEntity) where T : class; } /// @@ -144,6 +147,13 @@ public IDictionary Clear() return ret; } + /// + public void Dispose(bool disposeSampleEntity) where T : class + { + GetPool().Dispose(disposeSampleEntity); + RemovePool(); + } + /// public void Dispose() { diff --git a/Tests/Editor/PlayMode/CoroutineServiceTest.cs b/Tests/Editor/PlayMode/CoroutineServiceTest.cs index 30e8f42..634fc82 100644 --- a/Tests/Editor/PlayMode/CoroutineServiceTest.cs +++ b/Tests/Editor/PlayMode/CoroutineServiceTest.cs @@ -67,8 +67,8 @@ public IEnumerator StartAsyncCoroutine_WithData_Successfully() const int testValue2 = 10; int testCompleted = 0; - var asyncCoroutine = _coroutineService.StartAsyncCoroutine(TestCoroutine(testValue1)); - asyncCoroutine.OnComplete(testValue2, newValue => testCompleted = newValue); + var asyncCoroutine = _coroutineService.StartAsyncCoroutine(TestCoroutine(testValue1), testValue2); + asyncCoroutine.OnComplete(newValue => testCompleted = newValue); yield return asyncCoroutine.Coroutine; diff --git a/package.json b/package.json index cd85561..9623b93 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.gamelovers.services", "displayName": "Services", "author": "Miguel Tomas", - "version": "0.14.1", + "version": "0.15.0", "unity": "2022.3", "license": "MIT", "description": "The purpose of this package is to provide a set of services to ease the development of a basic game architecture",