Skip to content

Commit

Permalink
View prefab providers (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
kekchpek authored Dec 1, 2024
2 parents 2fb450b + fc255c7 commit e5eaf35
Show file tree
Hide file tree
Showing 15 changed files with 108 additions and 40 deletions.
24 changes: 13 additions & 11 deletions MvvmUnityProj/CCG/Assets/Code/Core/CoreInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using UnityMVVM.DI;
using UnityMVVM.ViewManager;
using UnityMVVM.ViewModelCore;
using UnityMVVM.ViewModelCore.PrefabsProvider;
using Zenject;

namespace CCG.Core
Expand All @@ -34,27 +35,28 @@ public override void InstallBindings()
{

Container.Decorate<IViewManager>().With<LogViewManagerDecorator>();

Container.Bind<IViewsPrefabsProvider>().To<ResourcesPrefabProvider>().AsSingle();
Container.ProvideAccessForViewModelLayer<IViewsPrefabsProvider>();

Container.InstallPoolableView<MainScreenView, IMainScreenViewModel, MainScreenViewModel>(ViewNames.MainScreen,
() => Resources.Load<GameObject>("Prefabs/Views/MainScreenView"));
Container.InstallView<MainScreen3dView, IViewModel, ViewModel>(ViewNames.MainScreen3d,
() => Resources.Load<GameObject>("Prefabs/Views/MainScreen3dView"));
Container.InstallPoolableView<MainScreenView, IMainScreenViewModel, MainScreenViewModel>(ViewNames.MainScreen);
Container.InstallView<MainScreen3dView, IViewModel, ViewModel>(ViewNames.MainScreen3d);
Container.InstallView<StatsChangerView, IStatsChangerViewModel, StatsChangerViewModel>();
Container.InstallView<PlayButtonView, IPlayButtonViewModel, PlayButtonViewModel>();
Container.InstallView<HandControllerView, IHandControllerViewModel, HandControllerViewModel>(ViewNames.HandController,
() => Resources.Load<GameObject>("Prefabs/Views/HandController"));
_ => Resources.Load<GameObject>("Prefabs/Views/HandController"));
Container.InstallPoolableView<CardView, ICardViewModel, CardViewModel>(ViewNames.Card,
() => Resources.Load<GameObject>("Prefabs/Views/CardView"));
_ => Resources.Load<GameObject>("Prefabs/Views/CardView"));
Container.InstallView<MainMenuView3d, IMainMenuViewModel3d, MainMenuViewModel3d>(ViewNames.MainMenu3d,
() => Resources.Load<GameObject>("Prefabs/Views/MainMenu3d/MainMenu3dScene"));
_ => Resources.Load<GameObject>("Prefabs/Views/MainMenu3d/MainMenu3dScene"));
Container.InstallView<MainMenuViewUi, IMainMenuViewModelUi, MainMenuViewModelUi>(ViewNames.MainMenuUi,
() => Resources.Load<GameObject>("Prefabs/Views/MainMenuUi/MainMenuUi"));
_ => Resources.Load<GameObject>("Prefabs/Views/MainMenuUi/MainMenuUi"));
Container.InstallView<LoadingPopupView, IViewModel, ViewModel>(ViewNames.LoadingPopup,
() => Resources.Load<GameObject>("Prefabs/Views/LoadingPopup"));
_ => Resources.Load<GameObject>("Prefabs/Views/LoadingPopup"));
Container.InstallView<CoolPopupView, ICoolPopupViewModel, CoolPopupViewModel>(ViewNames.CoolPopup,
() => Resources.Load<GameObject>("Prefabs/Views/CoolPopup/CoolPopup"));
_ => Resources.Load<GameObject>("Prefabs/Views/CoolPopup/CoolPopup"));
Container.InstallView<CoolPopupView, ICoolPopupViewModel, CoolPopupViewModel>(ViewNames.SameCoolPopupButWithOtherName,
() => Resources.Load<GameObject>("Prefabs/Views/CoolPopup/CoolPopup"));
_ => Resources.Load<GameObject>("Prefabs/Views/CoolPopup/CoolPopup"));
Container.InstallView<TimeCounterView, ITimeCounterViewModel, TimeCounterViewModel>();

Container.Install<ImageSystemInstaller>();
Expand Down
1 change: 1 addition & 0 deletions MvvmUnityProj/CCG/Assets/Code/Core/GameInstaller.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using UnityEngine;
using UnityMVVM.DI;
using UnityMVVM.DI.Config;
using Zenject;

namespace CCG.Core
Expand Down
13 changes: 13 additions & 0 deletions MvvmUnityProj/CCG/Assets/Code/Core/ResourcesPrefabProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using UnityEngine;
using UnityMVVM.ViewModelCore.PrefabsProvider;

namespace CCG.Core
{
public class ResourcesPrefabProvider : IViewsPrefabsProvider
{
public GameObject GetViewPrefab(string viewName)
{
return Resources.Load<GameObject>($"Prefabs/Views/{viewName}View");
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified MvvmUnityProj/CCG/Assets/Packages/com.kekchpek.umvvm/UnityMVVM.dll
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public override void InstallBindings()
},
new MvvmContainerConfiguration
{
ViewFactory = new TestViewFactory()
viewFactory = new TestViewFactory()
});
Container.Install<CoreInstaller>();

Expand Down
Binary file modified MvvmUnityProj/QuickStartUnityMVVM/Assets/Libs/UnityMVVM.dll
Binary file not shown.
2 changes: 1 addition & 1 deletion src/UnityMVVM/DI/Config/MvvmContainerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ public struct MvvmContainerConfiguration
/// <summary>
/// The view factory to use for views creation and initialization.
/// </summary>
public IViewFactory? ViewFactory;
public IViewFactory? viewFactory;
}
}
40 changes: 22 additions & 18 deletions src/UnityMVVM/DI/DiContainerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using UnityMVVM.DI.Environment;
using UnityMVVM.DI.Mapper;
using UnityMVVM.Pool;
using UnityMVVM.ViewModelCore.PrefabsProvider;

// ReSharper disable MemberCanBePrivate.Global

Expand Down Expand Up @@ -73,13 +74,13 @@ public static void UseAsMvvmContainer(
container.Bind<IViewsModelsContainerAdapter>().FromInstance(viewModelsContainerAdapter);
container.FastBind<IViewManager, ViewManagerImpl>();

if (config.ViewFactory == null)
if (config.viewFactory == null)
{
viewModelsContainer.Bind<IViewFactory>().To<ViewFactory>().AsSingle();
}
else
{
viewModelsContainer.Bind<IViewFactory>().FromInstance(config.ViewFactory);
viewModelsContainer.Bind<IViewFactory>().FromInstance(config.viewFactory);
}

viewModelsContainer.Bind<IViewToViewModelMapper>().FromInstance(mapper);
Expand All @@ -100,47 +101,49 @@ public static void InstallView<TView, TViewModel, TViewModelImpl>(this DiContain
where TViewModel : class, IViewModel
where TViewModelImpl : class, TViewModel
{
container.InstallView<TView, TViewModel, TViewModelImpl>(viewName, () => viewPrefab);
container.InstallView<TView, TViewModel, TViewModelImpl>(viewName, _ => viewPrefab);
}

/// <summary>
/// Installs <see cref="IViewModelsFactory"/> for specified View-ViewModel pair.
/// </summary>
/// <param name="container">MVVM container to configure.</param>
/// <param name="viewName">View identificator for opening.</param>
/// <param name="viewPrefabGetter">The method to obtain view prefab. View should contains <typeparamref name="TView"/> component inside.</param>
/// <param name="viewPrefabGetter">
/// The method to obtain view prefab by its bound name. View should contains <typeparamref name="TView"/> component inside.
/// There should be default viewPrefabProvider specified in config on UseAsMvvmContainer call if this
/// parameter is set to null.
/// </param>
/// <typeparam name="TView">The type of a view</typeparam>
/// <typeparam name="TViewModel">The type of a view model.</typeparam>
/// <typeparam name="TViewModelImpl">The type, that implements a view model.</typeparam>
public static void InstallView<TView, TViewModel, TViewModelImpl>(this DiContainer container, string viewName, Func<GameObject> viewPrefabGetter)
public static void InstallView<TView, TViewModel, TViewModelImpl>(this DiContainer container, string viewName, Func<string, GameObject>? viewPrefabGetter = null)
where TView : ViewBehaviour<TViewModel>
where TViewModel : class, IViewModel
where TViewModelImpl : class, TViewModel
{
if (viewPrefabGetter == null) throw new ArgumentNullException(nameof(viewPrefabGetter));
InstallViewInternal<TView, TViewModel, TViewModelImpl>(container, viewName, viewPrefabGetter, null);
InstallViewInternal<TView, TViewModel, TViewModelImpl>(container, viewName, null, viewPrefabGetter);
}

/// <inheritdoc cref="InstallView{TView,TViewModel,TViewModelImpl}(Zenject.DiContainer,string,Func{UnityEngine.GameObject})"/>
/// <inheritdoc cref="InstallView{TView,TViewModel,TViewModelImpl}(Zenject.DiContainer,string,Func{string, UnityEngine.GameObject})"/>
/// <param name="viewPool">The pool for views. Uses default <see cref="ViewPool{T}"/> object if null specified.</param>
public static void InstallPoolableView
<TView, TViewModel, TViewModelImpl>
#pragma warning disable CS1573
(this DiContainer container,
string viewName,
Func<GameObject> viewPrefabGetter,
Func<string, GameObject>? viewPrefabGetter = null,
#pragma warning restore CS1573
IViewPool? viewPool = null)
where TView : ViewBehaviour<TViewModel>, IPoolableView
where TViewModel : class, IViewModel
where TViewModelImpl : class, TViewModel
{
if (viewPrefabGetter == null) throw new ArgumentNullException(nameof(viewPrefabGetter));
InstallViewInternal<TView, TViewModel, TViewModelImpl>(
container,
viewName,
viewPrefabGetter,
viewPool ?? new ViewPool<TView>());
viewPool ?? new ViewPool<TView>(),
viewPrefabGetter);
}

/// <inheritdoc cref="InstallView{TView,TViewModel,TViewModelImpl}(Zenject.DiContainer,string,UnityEngine.GameObject)"/>
Expand All @@ -160,31 +163,32 @@ public static void InstallPoolableView
InstallViewInternal<TView, TViewModel, TViewModelImpl>(
container,
viewName,
() => viewPrefab,
viewPool ?? new ViewPool<TView>());
viewPool ?? new ViewPool<TView>(),
_ => viewPrefab);
}

private static void InstallViewInternal<TView, TViewModel, TViewModelImpl>(
DiContainer container, string viewName,
Func<GameObject> viewPrefabGetter, IViewPool? viewPool)
DiContainer container, string viewName, IViewPool? viewPool,
Func<string, GameObject>? viewPrefabGetter = null)
where TView : ViewBehaviour<TViewModel>
where TViewModel : class, IViewModel
where TViewModelImpl : class, TViewModel
{
if (!ContainerEnvironments.TryGetValue(container, out var env))
{
throw new InvalidOperationException($"Provided container does not contain container for the view-model layer. " +
$"Use {nameof(UseAsMvvmContainer)} method to configure container.");
$"Use {nameof(UseAsMvvmContainer)} method to configure the container.");
}
var viewModelsContainer = env.ViewsModelsContainerAdapter;
var extraArgs = new List<TypeValuePair>();
viewModelsContainer.Container
.Bind<IViewModelsFactory>()
.WithId(viewName)
.To<ViewModelsFactory<TView>>()
.AsTransient()
.WithArgumentsExplicit(new []
{
new TypeValuePair(typeof(Func<GameObject>), viewPrefabGetter),
new TypeValuePair(typeof(Func<string, GameObject>), viewPrefabGetter),
new TypeValuePair(typeof(IViewPool), viewPool),
});
env.Mapper.Map<TView, TViewModelImpl>();
Expand Down
2 changes: 2 additions & 0 deletions src/UnityMVVM/DI/Environment/ContainerEnvironment.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using UnityEngine;
using UnityMVVM.DI.Mapper;

namespace UnityMVVM.DI.Environment
Expand Down
2 changes: 2 additions & 0 deletions src/UnityMVVM/DI/Environment/IContainerEnvironment.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using UnityEngine;
using UnityMVVM.DI.Mapper;

namespace UnityMVVM.DI.Environment
Expand Down
4 changes: 2 additions & 2 deletions src/UnityMVVM/ViewManager/ViewManagerImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public IReadOnlyList<string> GetLayerIds()

public IViewModel Create(IViewModel parent, string viewName, Transform container, IPayload? payload = null)
{
return _viewsContainer.ResolveViewFactory(viewName).Create(parent.Layer, parent, container, payload);
return _viewsContainer.ResolveViewFactory(viewName).Create(parent.Layer, viewName, parent, container, payload);
}

/// <inheritdoc cref="IViewManager.Open(string, string, IPayload)"/>
Expand Down Expand Up @@ -188,7 +188,7 @@ public IViewModel Create(IViewModel parent, string viewName, Transform container

private IViewModel CreateViewOnLayer(string viewName, IViewLayer layer, IPayload? payload)
{
var viewModel = _viewsContainer.ResolveViewFactory(viewName).Create(layer, null, layer.Container, payload);
var viewModel = _viewsContainer.ResolveViewFactory(viewName).Create(layer, viewName, null, layer.Container, payload);
_createdViewsNames.Add(viewModel, viewName);
viewModel.Destroyed += OnViewModelDestroyed;
layer.Set(viewModel);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using UnityEngine;

namespace UnityMVVM.ViewModelCore.PrefabsProvider
{
/// <summary>
/// The provider for view prefabs, that should be used in case of missing explicit prefab getter.
/// </summary>
public interface IViewsPrefabsProvider
{
/// <summary>
/// Returns a prefab for specified view name.
/// </summary>
/// <param name="viewName">The name of a view to obtain prefab.</param>
/// <returns>The view prefab.</returns>
GameObject GetViewPrefab(string viewName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ internal interface IViewModelsFactory
/// Creates view and its view model
/// </summary>
/// <param name="viewLayer">Layer to place a view.</param>
/// <param name="viewName">The name of view to create.</param>
/// <param name="parent">Parent view model to set to the created view model.</param>
/// <param name="parentTransform">The transform to instantiate the view to.</param>
/// <param name="payload">View model payload.</param>
/// <returns>Returns created view model to control the view.</returns>
IViewModel Create(IViewLayer viewLayer,
IViewModel Create(IViewLayer viewLayer,
string viewName,
IViewModel? parent,
Transform parentTransform,
IPayload? payload = null);
Expand Down
34 changes: 28 additions & 6 deletions src/UnityMVVM/ViewModelCore/ViewModelsFactory/ViewModelsFactory.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ModestTree;
using UnityEngine;
using UnityEngine.Scripting;
using UnityMVVM.DI.Mapper;
using UnityMVVM.Pool;
using UnityMVVM.ViewManager.ViewLayer;
using UnityMVVM.ViewModelCore.PrefabsProvider;
using Zenject;

namespace UnityMVVM.ViewModelCore.ViewModelsFactory
Expand All @@ -20,8 +20,9 @@ internal class ViewModelsFactory<TView> : IViewModelsFactory
private readonly IInstantiator _instantiator;
private readonly IViewToViewModelMapper _viewToViewModelMapper;
private readonly IViewFactory _viewFactory;
private readonly Func<GameObject> _viewPrefabGetter;
private readonly Func<string, GameObject>? _viewPrefabGetter;
private readonly IViewPool? _viewPool;
private readonly IViewsPrefabsProvider? _viewsPrefabsProvider;

/// <summary>
/// Default constructor for view factory.
Expand All @@ -31,28 +32,49 @@ internal class ViewModelsFactory<TView> : IViewModelsFactory
/// <param name="viewToViewModelMapper">Map of views and view models types.</param>
/// <param name="viewFactory">The view factory to create and initialize views.</param>
/// <param name="viewPool">The pool for views(if presented)</param>
/// <param name="viewsPrefabsProvider">The default provider for views prefabs.</param>
[Preserve]
public ViewModelsFactory(
Func<GameObject> viewPrefabGetter,
Func<string, GameObject>? viewPrefabGetter,
IInstantiator instantiator,
IViewToViewModelMapper viewToViewModelMapper,
IViewFactory viewFactory,
IViewPool viewPool)
IViewPool viewPool,
[InjectOptional] IViewsPrefabsProvider viewsPrefabsProvider)
{
_viewPrefabGetter = viewPrefabGetter;
_instantiator = instantiator;
_viewToViewModelMapper = viewToViewModelMapper;
_viewFactory = viewFactory;
_viewPool = viewPool;
_viewsPrefabsProvider = viewsPrefabsProvider;
}

/// <inheritdoc cref="IViewModelsFactory.Create(IViewLayer, IViewModel, Transform, IPayload)"/>
/// <inheritdoc cref="IViewModelsFactory.Create(IViewLayer, string, IViewModel, Transform, IPayload)"/>
public IViewModel Create(IViewLayer viewLayer,
string viewName,
IViewModel? parent,
Transform transform,
IPayload? payload = null)
{
var view = _viewFactory.Instantiate<TView>(_viewPrefabGetter.Invoke(), transform, _viewPool);
GameObject viewPrefab;
if (_viewPrefabGetter != null)
{
viewPrefab = _viewPrefabGetter.Invoke(viewName);
}
else
{
if (_viewsPrefabsProvider != null)
{
viewPrefab = _viewsPrefabsProvider!.GetViewPrefab(viewName);
}
else
{
throw new Exception(
"There should be either getter for prefab or default views prefabs provider bound.");
}
}
var view = _viewFactory.Instantiate<TView>(viewPrefab, transform, _viewPool);

if (view is not Component c)
throw new Exception("View should be a Component");
Expand Down

0 comments on commit e5eaf35

Please sign in to comment.