modesttree / Zenject

Dependency Injection Framework for Unity3D
MIT License
2.54k stars 273 forks source link

MemoryPool doesn't allow to use backing factory with multiple params, e.g. PrefabResourceFactory #16

Open waitxd opened 5 years ago

waitxd commented 5 years ago

In current implementation it seems impossible to create MonoMemoryPool and provide the prefab to use for the new object (like with PrefabResourceFactory or PrefabFactory). Even when you are instantiating memory pool directly.

waitxd commented 5 years ago

Spent some time to solve this problem.

Right now in Extenject each of MonoMemoryPool<TParam1..TParam5,TContract> (one till five parameters) classes is using MemoryPoolBase<TContract> with only one parameter, which holds IFactory<TContract> (no parameters in Create method). So there is no way to pass additional parameters to the factrory. This might be a good place for further improvements.

However, I came to the conclusion that the idea of using MonoMemoryPool with Prefab[Resource]Factory is broken by design. If you pass different prefabs to the MonoMemoryPool.Spawn, the pool will contain different prefabs after MonoMemoryPool.Despawn. This can lead to the undefined behaviour when someone will reuse them.

But if someone really need to pass parameters from memory pool to underlaying factory you can use something like that.

svermeulen commented 5 years ago

Yep, that all makes sense. You can't use the same memory pool to share instances that are created using multiple different prefabs. One issue I see with your solution is that using code could pass different prefabs. Which might not be an issue if you're injecting it AsTransient but worth pointing out. Another solution would be to use a dictionary like this:

    public class BulletFactory 
    {
        readonly Dictionary<GameObject, IMemoryPool<Bullet>> _pools =
            new Dictionary<GameObject, IMemoryPool<Bullet>>();

        readonly DiContainer _container;
        readonly MemoryPoolSettings _settings;

        public BulletFactory(DiContainer container, MemoryPoolSettings settings)
        {
            _container = container;
            _settings = settings;
        }

        public void Despawn(GameObject prefab, Bullet bullet)
        {
            _pools[prefab].Despawn(bullet);
        }

        public Bullet Spawn(GameObject prefab) 
        {
            IMemoryPool<Bullet> pool;

            if (!_pools.TryGetValue(prefab, out pool)) 
            {
                pool = _container.Instantiate<MonoMemoryPool<Bullet>>(
                    new object[] { _settings,  new FuncFactory<Bullet>(() => _container.InstantiatePrefabForComponent<Bullet>(prefab))});
                _pools.Add(prefab, pool);
            }

            return pool.Spawn();
        }
    }

    public class FuncFactory<T> : IFactory<T>
    {
        readonly Func<T> _factoryMethod;

        public FuncFactory(Func<T> factoryMethod)
        {
            _factoryMethod = factoryMethod;
        }

        public T Create() 
        {
            return _factoryMethod();
        }
    }
kennir commented 4 years ago

@svermeulen Hi , Can you explain how to use BulletFactory ? I can not use BindFactory<Bullet, BulletFactory> because BulletFactory is not inherit from PlaceholdFactory?