Mathijs-Bakker / Extenject

Dependency Injection Framework for Unity Game Engine
MIT License
531 stars 97 forks source link

Removing Boiler Plate for Spawning Prefabs. #78

Open SimonNordon4 opened 1 year ago

SimonNordon4 commented 1 year ago

Right now it's a lot of setup to just spawn a prefab.

The class looks like this:

public class Before : MonoBehaviour, IPoolable<IMemoryPool>, IDisposable
        {
            private IMemoryPool _pool;

            public void OnDespawned()
            {
                _pool = null;
            }

            public void OnSpawned(IMemoryPool p1)
            {
                _pool = p1;
            }

            public void Dispose()
            {
                _pool.Despawn(this);
            }

            public class Factory : PlaceholderFactory<Before>
            { }
            public class Pool : MonoPoolableMemoryPool<IMemoryPool, Before>
            { }
        }

        public class AfterInstaller : MonoInstaller
        {
            public After prefab;

            public override void InstallBindings()
            {
                Extensions.BindPoolable<After>(Container,
                    c => c.WithInitialSize(5)
                        .FromComponentInNewPrefab(prefab)
                        .UnderTransformGroup("After")
                        .AsCached());
            }
        }

        public class After : PoolableMonoBehaviour<After>
        {

        }

and the binding looks like this

        public class BeforeInstaller : MonoInstaller
        {
            public Before prefab;

            public override void InstallBindings()
            {
                Container.BindFactory<Before, Before.Factory>()
                    .FromPoolableMemoryPool<Before, Before.Pool>(pool => pool
                        .WithInitialSize(5)
                        .FromComponentInNewPrefab(prefab)
                        .UnderTransformGroup("Before")
                        .AsCached());
            }
        }

I wrote a wrapper class for this kind of thing.

        public abstract class PoolableMonoBehaviour<T> : MonoBehaviour, IPoolable<IMemoryPool>, IDisposable where T : PoolableMonoBehaviour<T>
        {
            protected IMemoryPool _pool;

            public void OnSpawned(IMemoryPool pool)
            {
                _pool = pool;
                OnSpawnedInternal();
            }

            public virtual void OnSpawnedInternal()
            {

            }

            public void OnDespawned()
            {
                OnDespawnedInternal();
            }

            public virtual void OnDespawnedInternal()
            {

            }

            public virtual void Dispose() => _pool.Despawn(this);

            public class Factory : PlaceholderFactory<T>
            { }

            public class Pool : MonoPoolableMemoryPool<IMemoryPool, T>
            { }
        }

Right now it doesn't support parameters, but that would be easy to do, just duplicate the class and add TParam infront of Every IMermoryPool.

I also wrote an extension method for binding

namespace Zenject.Extensions  
{
    public static class Extensions
    {
        public static ArgConditionCopyNonLazyBinder BindPoolable<TPoolable>
            (DiContainer container,Action<MemoryPoolInitialSizeMaxSizeBinder<TPoolable>> poolBinder)
        where TPoolable : PoolableMonoBehaviour<TPoolable>
        {
            var x = container
                .BindFactory<TPoolable, PoolableMonoBehaviour<TPoolable>.Factory>()
                .FromPoolableMemoryPool<TPoolable, PoolableMonoBehaviour<TPoolable>.Pool>
                    (poolBinder);
            return x;
        }
    }
}

With these new scripts, the code goes from looking like it did at the top to this:

        public class AfterInstaller : MonoInstaller
        {
            public After prefab;

            public override void InstallBindings()
            {
                Extensions.BindPoolable<After>(Container,
                    c => c.WithInitialSize(5)
                        .FromComponentInNewPrefab(prefab)
                        .UnderTransformGroup("After")
                        .AsCached());
            }
        }

        public class After : PoolableMonoBehaviour<After>
        {

        }

This makes it mech easier to for a gameobject to return itself to the pool

        public class After : PoolableMonoBehaviour<After>
        {
            private float lifetime = 1f;
            public override void OnSpawnedInternal()
            {
                lifetime = 1f;
            }

            private void Update()
            {
                lifetime -= Time.deltaTime;
                if (lifetime <= 0)
                {
                    Dispose();
                }
            }
        }

This isn't really an issue, just now that Extenject isn't being supported I thought it would be useful for people trying to wrap their heads around prefab spawning.