Home > front end >  Use Zenject FromSubContainerResolve on ProjectContext to SceneContext
Use Zenject FromSubContainerResolve on ProjectContext to SceneContext

Time:02-05

I have a Unity Zenject setup with a ProjectInstaller with some global dependencies that adhere to a "modal" interface, e.g.,

public class ProjectInstaller : MonoInstaller {
  public override void InstallBindings() {
    Container.Bind<ModalManager>().AsSingle();

    Container.Bind<Modal>().To<DialogManager>().AsSingle();
  }
}

Some modals are only relevant to certain scenes, so I bind those in the SceneInstaller:

public class SceneInstaller : MonoInstaller {
  public override void InstallBindings() {
    Container.BindInterfacesAndSelfTo<InventoryManager>()
      .FromComponentInNewPrefab(InventoryPrefab)
      .AsSingle()
  }
}

I want to manage all modals from the single ModalManager, defined at the project scope. So it has a List<Modal> binding:

public class ModalManager : MonoBehaviour {
    [Inject]
    protected List<Modal> _modals;
}

When I run this, the ModalManager only gets a single modal: the one defined in the project scope. In my understanding the SceneContext is a subcontainer of the ProjectContext. So I should be able to use FromSubContainerResolve in the ProjectInstaller to bind items in the child scene, perhaps by adding a line like:

// ProjectInstaller.cs
public override void InstallBindings() {
  // ...
  Container.Bind<Modal>().To<InventoryManager>().FromSubContainerResolve();
}

But I'm not sure which of the eleventy FromSubContainerResolve methods make sense for this case. They all seem pertinent to prefabs with a game object context, not for use from within the ProjectContext.

Does this use case make sense? Is there an easier or better way?

CodePudding user response:

The problem is that that ModalManager can only be injected with dependencies that are added directly to ProjectContext. For these kinds of problems I recommend using the following pattern:

public interface IModal
{
}

public class ModalManager
{
    private readonly List<IModal> _modals = new List<IModal>();

    public IReadOnlyList<IModal> Modals
    {
        get { return _modals; }
    }

    public void AddModal(IModal modal)
    {
        _modals.Add(modal);
    }

    public bool RemoveModal(IModal modal)
    {
        return _modals.Remove(modal);
    }
}

public class ModalRegisterHandler : IInitializable, IDisposable
{
    private readonly List<IModal> _modals;
    private readonly ModalManager _modalManager;

    public ModalRegisterHandler(
        // We need to use InjectSources.Local here, otherwise we will
        // add any project context modals again in each scene
        [Inject(Source = InjectSources.Local)]
        List<IModal> modals, ModalManager modalManager)
    {
        _modals = modals;
        _modalManager = modalManager;
    }

    public void Initialize()
    {
        foreach (var modal in _modals)
        {
            _modalManager.AddModal(modal);
        }
    }

    public void Dispose()
    {
        // We don't want ModalManager to retain references to Modals defined in unloaded scenes
        // (dispose is executed on scene unload)
        foreach (var modal in _modals)
        {
            _modalManager.RemoveModal(modal);
        }
    }
}

public class SceneInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<IModal>().To<FooModal>();
        Container.Bind<IModal>().To<BarModal>();
    }
}

public class ProjectInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        // We use CopyIntoDirectSubContainers so that ModalRegisterHandler gets automatically added to every 
        // scene context
        Container.BindInterfacesTo<ModalRegisterHandler>().AsSingle().CopyIntoDirectSubContainers();

        Container.Bind<ModalManager>().AsSingle();

        Container.Bind<IModal>().To<QuxModal>();
        Container.Bind<IModal>().To<FizzModal>();
    }
}
  •  Tags:  
  • Related