Skip to content

Commit

Permalink
feat!: rework dependency injection, create AssetDatabaseAssetLoader
Browse files Browse the repository at this point in the history
BREAKING CHANGE: InjectDependencyAttribute has been renamed
to DependencyAttribute. The provider argument has been renamed
to provide. The majority of DependencyInjector has been reworked
completely.
  • Loading branch information
jonisavo committed Apr 29, 2022
1 parent 336b6b9 commit 935703e
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 93 deletions.
12 changes: 12 additions & 0 deletions Core/AssetDatabaseAssetLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using UnityEditor;

namespace UIComponents.Core
{
public class AssetDatabaseAssetLoader : IAssetLoader
{
public T LoadAsset<T>(string assetPath) where T : UnityEngine.Object
{
return AssetDatabase.LoadAssetAtPath<T>(assetPath);
}
}
}
3 changes: 3 additions & 0 deletions Core/AssetDatabaseAssetLoader.cs.meta

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

18 changes: 18 additions & 0 deletions Core/DependencyAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace UIComponents.Core
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependencyAttribute : Attribute
{
public readonly Type DependencyType;

public readonly Type ProvideType;

public DependencyAttribute(Type dependency, Type provide)
{
DependencyType = dependency;
ProvideType = provide;
}
}
}
File renamed without changes.
94 changes: 68 additions & 26 deletions Core/DependencyInjector.cs
Original file line number Diff line number Diff line change
@@ -1,62 +1,104 @@
using System;
using System.Collections.Generic;
using UnityEngine;

namespace UIComponents.Core
{
public class DependencyInjector
{
private static readonly Dictionary<Type, object> InstantiatedInstanceDictionary
internal static readonly Dictionary<Type, DependencyInjector> InjectorDictionary =
new Dictionary<Type, DependencyInjector>();

internal static readonly Dictionary<Type, object> InstantiatedInstanceDictionary
= new Dictionary<Type, object>();

private readonly Dictionary<Type, object> _dependencyDictionary
internal readonly Dictionary<Type, object> DependencyDictionary
= new Dictionary<Type, object>();

internal void AddProvidersFromDependencies(IEnumerable<InjectDependencyAttribute> dependencyAttributes)
public static void SetDependency<TConsumer, TDependency>(TDependency provider)
where TConsumer : class
where TDependency : class
{
foreach (var dependencyAttribute in dependencyAttributes)
{
var type = dependencyAttribute.DependencyType;
var providerType = dependencyAttribute.ProviderType;

if (!_dependencyDictionary.ContainsKey(type))
_dependencyDictionary[type] = CreateInstance(providerType);
}
var injector = GetInjector(typeof(TConsumer));

injector.SetDependency(provider);
}

public static TDependency GetDependency<TConsumer, TDependency>()
where TConsumer : class
where TDependency : class
{
var injector = GetInjector(typeof(TConsumer));

return injector.Provide<TDependency>();
}

public static DependencyInjector GetInjector(Type consumerType)
{
if (InjectorDictionary.ContainsKey(consumerType))
return InjectorDictionary[consumerType];

return CreateInjector(consumerType);
}

private static object CreateInstance(Type providerType)
private static DependencyInjector CreateInjector(Type consumerType)
{
var injector = new DependencyInjector();

var injectAttributes = (DependencyAttribute[])
consumerType.GetCustomAttributes(typeof(DependencyAttribute), true);

injector.PopulateFromDependencyAttributes(injectAttributes);

InjectorDictionary.Add(consumerType, injector);

return injector;
}

private static object CreateInstance(Type dependencyType)
{
object instance;

if (InstantiatedInstanceDictionary.ContainsKey(providerType))
if (InstantiatedInstanceDictionary.ContainsKey(dependencyType))
{
instance = InstantiatedInstanceDictionary[providerType];
instance = InstantiatedInstanceDictionary[dependencyType];
}
else
{
instance = Activator.CreateInstance(providerType);
InstantiatedInstanceDictionary[providerType] = instance;
instance = Activator.CreateInstance(dependencyType);
InstantiatedInstanceDictionary[dependencyType] = instance;
}

return instance;
}

public void SetProvider<T>(T instance)
public void SetDependency<T>(T instance) where T : class
{
_dependencyDictionary[typeof(T)] = instance;
DependencyDictionary[typeof(T)] = instance;
}

public T Provide<T>()
public T Provide<T>() where T : class
{
var type = typeof(T);

if (!_dependencyDictionary.ContainsKey(type))

if (DependencyDictionary.ContainsKey(type))
return (T) DependencyDictionary[type];

Debug.LogWarningFormat("Could not get dependency {0}", type.Name);

return null;
}

private void PopulateFromDependencyAttributes(IEnumerable<DependencyAttribute> dependencyAttributes)
{
foreach (var dependencyAttribute in dependencyAttributes)
{
var value = (T) CreateInstance(type);
SetProvider(value);
return value;
var type = dependencyAttribute.DependencyType;
var providerType = dependencyAttribute.ProvideType;

if (!DependencyDictionary.ContainsKey(type))
DependencyDictionary[type] = CreateInstance(providerType);
}

return (T) _dependencyDictionary[type];
}
}
}
7 changes: 7 additions & 0 deletions Core/IAssetLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace UIComponents.Core
{
public interface IAssetLoader
{
public T LoadAsset<T>(string assetPath) where T : UnityEngine.Object;
}
}
3 changes: 3 additions & 0 deletions Core/IAssetLoader.cs.meta

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

20 changes: 0 additions & 20 deletions Core/InjectDependencyAttribute.cs

This file was deleted.

22 changes: 11 additions & 11 deletions Core/PathAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;

namespace UIComponents.Core
Expand Down Expand Up @@ -33,7 +35,7 @@ private bool TryGetPathFromComponent(UIComponent component, out string path)
{
path = "";

return TryGetValidAssetPathFromAttributes(component.AssetPathAttributes, out path);
return TryGetValidAssetPath(component.GetAssetPaths(), out path);
}

private bool TryGetPathFromAssembly(UIComponent component, out string path)
Expand All @@ -42,24 +44,22 @@ private bool TryGetPathFromAssembly(UIComponent component, out string path)

var assembly = component.GetType().Assembly;

var assetPathAttributes =
assembly.GetCustomAttributes(typeof(AssetPathAttribute), false);
var paths =
assembly.GetCustomAttributes(typeof(AssetPathAttribute), false)
.Select(attribute => ((AssetPathAttribute)attribute).Path );

return TryGetValidAssetPathFromAttributes((AssetPathAttribute[]) assetPathAttributes, out path);
return TryGetValidAssetPath(paths, out path);
}

private bool TryGetValidAssetPathFromAttributes(
AssetPathAttribute[] attributes,
private bool TryGetValidAssetPath(
IEnumerable<string> paths,
out string path)
{
path = "";

if (attributes.Length == 0)
return false;

foreach (var attribute in attributes)
foreach (var pathPart in paths)
{
var filePath = string.Join("/", attribute.Path, Path);
var filePath = string.Join("/", pathPart, Path);

if (string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(filePath)))
continue;
Expand Down
78 changes: 42 additions & 36 deletions Core/UIComponent.cs
Original file line number Diff line number Diff line change
@@ -1,77 +1,83 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;

namespace UIComponents.Core
{
[Dependency(typeof(IAssetLoader), provide: typeof(AssetDatabaseAssetLoader))]
public abstract class UIComponent : VisualElement
{
private static readonly Dictionary<Type, LayoutAttribute> LayoutAttributeDictionary =
new Dictionary<Type, LayoutAttribute>();
private static readonly Dictionary<Type, StylesheetAttribute[]> StylesheetAttributesDictionary =
new Dictionary<Type, StylesheetAttribute[]>();
private static readonly Dictionary<Type, AssetPathAttribute[]> AssetPathAttributesDictionary =
new Dictionary<Type, AssetPathAttribute[]>();

internal readonly DependencyInjector DependencyInjector;

private readonly IAssetLoader _assetLoader;

private readonly Type _componentType;

private readonly LayoutAttribute _layoutAttribute;

private readonly StylesheetAttribute[] _stylesheetAttributes;

internal readonly AssetPathAttribute[] AssetPathAttributes;

private static readonly Dictionary<Type, DependencyInjector> InjectorDictionary =
new Dictionary<Type, DependencyInjector>();

public static void SetDependencyProvider<TComponent, TDependency>(TDependency provider)
{
var componentType = typeof(TComponent);

if (!InjectorDictionary.ContainsKey(componentType))
InjectorDictionary.Add(componentType, new DependencyInjector());

InjectorDictionary[componentType].SetProvider(provider);
}

protected UIComponent()
{
_componentType = GetType();
_layoutAttribute = GetSingleAttribute<LayoutAttribute>();
AssetPathAttributes = GetAttributes<AssetPathAttribute>();
_stylesheetAttributes = GetAttributes<StylesheetAttribute>();
DependencyInjector = DependencyInjector.GetInjector(_componentType);

var type = GetType();
_assetLoader = DependencyInjector.Provide<IAssetLoader>();

if (!InjectorDictionary.ContainsKey(type))
InjectorDictionary.Add(type, new DependencyInjector());
if (!LayoutAttributeDictionary.ContainsKey(_componentType))
LayoutAttributeDictionary[_componentType] = GetSingleAttribute<LayoutAttribute>();

InjectorDictionary[type].AddProvidersFromDependencies(GetAttributes<InjectDependencyAttribute>());
if (!AssetPathAttributesDictionary.ContainsKey(_componentType))
AssetPathAttributesDictionary[_componentType] = GetAttributes<AssetPathAttribute>();

if (!StylesheetAttributesDictionary.ContainsKey(_componentType))
StylesheetAttributesDictionary[_componentType] = GetAttributes<StylesheetAttribute>();

LoadLayout();
LoadStyles();
}

protected T Provide<T>()
public IEnumerable<string> GetAssetPaths()
{
var assetPathAttributes = AssetPathAttributesDictionary[_componentType];

foreach (var assetPathAttribute in assetPathAttributes)
yield return assetPathAttribute.Path;
}

protected T Provide<T>() where T : class
{
return InjectorDictionary[_componentType].Provide<T>();
return DependencyInjector.Provide<T>();
}

[CanBeNull]
protected virtual VisualTreeAsset GetLayout()
{
if (_layoutAttribute == null)
var layoutAttribute = LayoutAttributeDictionary[_componentType];

if (layoutAttribute == null)
return null;

var assetPath = _layoutAttribute.GetAssetPathForComponent(this);
var assetPath = layoutAttribute.GetAssetPathForComponent(this);

return AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(assetPath);
return _assetLoader.LoadAsset<VisualTreeAsset>(assetPath);
}

protected virtual StyleSheet[] GetStyleSheets()
{
var loadedStyleSheets = new StyleSheet[_stylesheetAttributes.Length];
var stylesheetAttributes = StylesheetAttributesDictionary[_componentType];

var loadedStyleSheets = new StyleSheet[stylesheetAttributes.Length];

for (var i = 0; i < _stylesheetAttributes.Length; i++)
for (var i = 0; i < stylesheetAttributes.Length; i++)
{
var assetPath = _stylesheetAttributes[i].GetAssetPathForComponent(this);
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(assetPath);
var assetPath = stylesheetAttributes[i].GetAssetPathForComponent(this);
var styleSheet = _assetLoader.LoadAsset<StyleSheet>(assetPath);

if (styleSheet == null)
{
Expand Down

0 comments on commit 935703e

Please sign in to comment.