Skip to content

Commit

Permalink
feat(IAssetResolver): change AssetExists return type to Task<bool>
Browse files Browse the repository at this point in the history
BREAKING CHANGE: IAssetResolver's AssetExists now returns Task<bool>.
  • Loading branch information
jonisavo committed Sep 18, 2022
1 parent 8a301f3 commit 3dfe624
Show file tree
Hide file tree
Showing 15 changed files with 141 additions and 72 deletions.
23 changes: 21 additions & 2 deletions Assets/Samples/Addressables/AddressablesExampleComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using UIComponents.Addressables;
using UnityEngine;
using UnityEngine.UIElements;

namespace UIComponents.Samples.Addressables
{
Expand All @@ -7,5 +9,22 @@ namespace UIComponents.Samples.Addressables
[Stylesheet("AddressablesExampleComponent.uss")]
[Stylesheet("Box.uss")]
[Dependency(typeof(IAssetResolver), provide: typeof(AddressableAssetResolver))]
public class AddressablesExampleComponent : UIComponent {}
}
public class AddressablesExampleComponent : UIComponent
{
private readonly Label _loadingLabel;

public AddressablesExampleComponent()
{
_loadingLabel = new Label("Loading...");
_loadingLabel.style.alignSelf = Align.Center;
_loadingLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
_loadingLabel.style.fontSize = 50;
Add(_loadingLabel);
}

public override void OnInit()
{
_loadingLabel.RemoveFromHierarchy();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
<ui:VisualElement class="white-box">
<ui:Label name="addressables-example-header" text="AddressablesExampleComponent" />
<ui:Label text="This is an example of a component loaded with Addressables." />
<ui:Label text="Since asset loading is asynchronous, you can implement a Loading screen while your assets load.
AddressableAssetResolver caches whether an asset exists or not; those checks are only done once. This means that
the first load will always take longer than subsequent loads." />
</ui:VisualElement>
</UXML>
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ public void InitializeComponentWithColdCache()
BenchmarkUtils.MeasureComponentInitWithColdCache<ComponentWithAssets>();
}
}
}
}
2 changes: 1 addition & 1 deletion Assets/UIComponents.Benchmarks/BenchmarkUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace UIComponents.Benchmarks
{
public static class BenchmarkUtils
{
public const string Version = "0.22.1.0";
public const string Version = "0.23.0.0";

private static SampleGroup[] GetProfilerMarkers()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public IEnumerator Should_Be_Able_To_Load_Existing_Asset()
);
}

[Test]
public void Should_Be_Able_To_Tell_If_Asset_Exists()
[UnityTest]
public IEnumerator Should_Be_Able_To_Tell_If_Asset_Exists()
{
Assert_Tells_If_Asset_Exists(
yield return Assert_Tells_If_Asset_Exists(
"Assets/UIComponents.Tests/Addressables/Assets/Component.uss"
);
Assert_Tells_If_Asset_Exists(
yield return Assert_Tells_If_Asset_Exists(
"Assets/UIComponents.Tests/Addressables/Assets/Component.uxml"
);
}
Expand Down
27 changes: 15 additions & 12 deletions Assets/UIComponents.Tests/AssetResolverTestSuite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,33 @@ protected IEnumerator Assert_Loads_Existing_Asset<TAsset>(string assetPath)
{
var assetResolver = new T();

var obj = assetResolver.LoadAsset<TAsset>(assetPath);
var loadTask = assetResolver.LoadAsset<TAsset>(assetPath);

Assert.That(obj, Is.Not.Null);

Assert.That(obj, Is.InstanceOf<Task<TAsset>>());
Assert.That(loadTask, Is.Not.Null);

yield return obj.AsEnumerator();
yield return loadTask.AsEnumerator();

Assert.That(obj.Result, Is.Not.Null);
Assert.That(loadTask.Result, Is.Not.Null);

Assert.That(obj.Result, Is.InstanceOf<TAsset>());
Assert.That(loadTask.Result, Is.InstanceOf<TAsset>());
}

protected void Assert_Tells_If_Asset_Exists(string assetPath)
protected IEnumerator Assert_Tells_If_Asset_Exists(string assetPath)
{
var assetResolver = new T();

var existingAssetExists =
var existingAssetExistsTask =
assetResolver.AssetExists(assetPath);
var nonExistantAssetDoesNotExist =
var nonExistantAssetDoesNotExistTask =
assetResolver.AssetExists("SomeOtherAsset");

Assert.That(existingAssetExists, Is.True);
Assert.That(nonExistantAssetDoesNotExist, Is.False);
Assert.That(existingAssetExistsTask, Is.Not.Null);
Assert.That(nonExistantAssetDoesNotExistTask, Is.Not.Null);

yield return Task.WhenAll(existingAssetExistsTask, nonExistantAssetDoesNotExistTask).AsEnumerator();

Assert.That(existingAssetExistsTask.Result, Is.True);
Assert.That(nonExistantAssetDoesNotExistTask.Result, Is.False);
}
}
}
54 changes: 36 additions & 18 deletions Assets/UIComponents.Tests/PathAttributeTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using NSubstitute;
using System.Collections;
using System.Threading.Tasks;
using NSubstitute;
using NUnit.Framework;
using UIComponents.Internal;
using UIComponents.Testing;
using UIComponents.Tests.Utilities;
using UnityEngine.TestTools;

namespace UIComponents.Tests
{
Expand Down Expand Up @@ -39,48 +43,62 @@ public void SetUp()
.Build();
}

[Test]
public void Should_Return_Empty_String_If_No_Path_Is_Configured()
[UnityTest]
public IEnumerator Should_Return_Empty_String_If_No_Path_Is_Configured()
{
var pathAttribute = new BasicPathAttribute(null);
var component = _testBed.CreateComponent<UIComponentWithNoAssetPaths>();

var path = pathAttribute.GetAssetPathForComponent(component);


var assetPathTask = pathAttribute.GetAssetPathForComponent(component);

yield return assetPathTask.AsEnumerator();

var path = assetPathTask.Result;

Assert.That(path, Is.EqualTo(""));
}

[Test]
public void Should_Ignore_AssetPath_In_Case_Of_Complete_Path()
[UnityTest]
public IEnumerator Should_Ignore_AssetPath_In_Case_Of_Complete_Path()
{
var assetsPathAttribute = new BasicPathAttribute("Assets/Asset");
var packagesPathAttribute = new BasicPathAttribute("Packages/Asset");
var component = _testBed.CreateComponent<UIComponentWithValidAssetPath>();

var assetPathTask = assetsPathAttribute.GetAssetPathForComponent(component);
var packagePathTask = packagesPathAttribute.GetAssetPathForComponent(component);

var assetPath = assetsPathAttribute.GetAssetPathForComponent(component);
var packagePath = packagesPathAttribute.GetAssetPathForComponent(component);
yield return Task.WhenAll(assetPathTask, packagePathTask).AsEnumerator();

var assetPath = assetPathTask.Result;
var packagePath = packagePathTask.Result;

Assert.That(assetPath, Is.EqualTo("Assets/Asset"));
Assert.That(packagePath, Is.EqualTo("Packages/Asset"));
}

[Test]
public void Should_Use_Valid_AssetPath_In_Case_Of_Incomplete_Path()
[UnityTest]
public IEnumerator Should_Use_Valid_AssetPath_In_Case_Of_Incomplete_Path()
{
var pathAttribute = new BasicPathAttribute("MyAsset");

_mockResolver.AssetExists("Assets/Valid/Path/MyAsset").Returns(true);
_mockResolver.AssetExists("Assets/Invalid/Path/MyAsset").Returns(false);

var componentWithValidPath = _testBed.CreateComponent<UIComponentWithValidAssetPath>();
var componentWithInvalidPath = _testBed.CreateComponent<UIComponentWithInvalidAssetPath>();

var filledPath = pathAttribute.GetAssetPathForComponent(componentWithValidPath);
var ignoredPath = pathAttribute.GetAssetPathForComponent(componentWithInvalidPath);
var filledPathTask = pathAttribute.GetAssetPathForComponent(componentWithValidPath);
var ignoredPathTask = pathAttribute.GetAssetPathForComponent(componentWithInvalidPath);

yield return Task.WhenAll(filledPathTask, ignoredPathTask).AsEnumerator();

var filledPath = filledPathTask.Result;
var ignoredPath = ignoredPathTask.Result;

Assert.That(filledPath, Is.EqualTo("Assets/Valid/Path/MyAsset"));
Assert.That(ignoredPath, Is.EqualTo("MyAsset"));
}
}
}
}
}
6 changes: 3 additions & 3 deletions Assets/UIComponents.Tests/ResourcesAssetResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public IEnumerator Should_Be_Able_To_Load_Existing_Asset()
);
}

[Test]
public void Should_Be_Able_To_Tell_If_Asset_Exists()
[UnityTest]
public IEnumerator Should_Be_Able_To_Tell_If_Asset_Exists()
{
Assert_Tells_If_Asset_Exists(
yield return Assert_Tells_If_Asset_Exists(
"UIComponentTests/LayoutAttributeTests"
);
}
Expand Down
4 changes: 2 additions & 2 deletions Assets/UIComponents.Tests/UIComponentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ private class MockAssetResolver : IAssetResolver
source.SetResult(ScriptableObject.CreateInstance<T>());
}

public bool AssetExists(string assetPath)
public Task<bool> AssetExists(string assetPath)
{
return true;
return Task.FromResult(true);
}
}

Expand Down
8 changes: 4 additions & 4 deletions Assets/UIComponents/Addressables/AddressableAssetResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace UIComponents.Addressables
{
/// <summary>
/// An IAssetResolver which loads assets with Addressables.
/// </summary>
/// <seealso cref="DependencyAttribute"/>
/// <seealso cref="UIComponent"/>
/// </summary>
public class AddressableAssetResolver : IAssetResolver
{
public async Task<T> LoadAsset<T>(string assetPath) where T : UnityEngine.Object
Expand All @@ -28,16 +28,16 @@ public class AddressableAssetResolver : IAssetResolver

return asset;
}
public bool AssetExists(string assetPath)

public async Task<bool> AssetExists(string assetPath)
{
var handle = UnityEngine.AddressableAssets.Addressables.LoadResourceLocationsAsync(assetPath);

IList<IResourceLocation> locations;

try
{
locations = handle.WaitForCompletion();
locations = await handle.Task;
}
finally
{
Expand Down
18 changes: 18 additions & 0 deletions Assets/UIComponents/Core/DependencyInjection/DiContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ public bool TryGetSingletonInstance(Type type, out object instance)
{
return _singletonInstanceDictionary.TryGetValue(type, out instance);
}

/// <summary>
/// Attempts to fetch a singleton instance.
/// </summary>
/// <typeparam name="T">Singleton type</typeparam>
/// <param name="instance">Singleton instance</param>
/// <returns>Whether the instance could be fetched</returns>
public bool TryGetSingletonInstance<T>(out T instance) where T : class
{
instance = null;

if (!_singletonInstanceDictionary.TryGetValue(typeof(T), out var obj))
return false;

instance = obj as T;

return instance != null;
}

/// <param name="instanceType">Type of the singleton instance</param>
/// <param name="instance">Singleton instance to add</param>
Expand Down
7 changes: 5 additions & 2 deletions Assets/UIComponents/Core/IAssetResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ public interface IAssetResolver
/// Returns whether the asset exists.
/// </summary>
/// <param name="assetPath">Asset path</param>
/// <returns>Whether the asset exists</returns>
bool AssetExists(string assetPath);
/// <returns>
/// A task which resolves to a boolean, which tells
/// whether the asset exists
/// </returns>
Task<bool> AssetExists(string assetPath);
}
}
31 changes: 15 additions & 16 deletions Assets/UIComponents/Core/PathAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using UnityEditor;
using UnityEngine;
using System.Threading.Tasks;

namespace UIComponents
{
Expand All @@ -21,14 +20,16 @@ public abstract class PathAttribute : Attribute
/// The first valid path is chosen.
/// </summary>
/// <param name="component">UIComponent to get path for</param>
/// <returns>Asset path used by component</returns>
public string GetAssetPathForComponent(UIComponent component)
/// <returns>A Task which resolves to the asset path used by the component</returns>
public async Task<string> GetAssetPathForComponent(UIComponent component)
{
if (Path == null)
return string.Empty;

if (!ConfiguredPathIsComplete() && TryGetPathFromComponent(component, out var path))
return path;
var componentAssetPath = await GetPathFromComponent(component);

if (!ConfiguredPathIsComplete() && !string.IsNullOrEmpty(componentAssetPath))
return componentAssetPath;

return Path;
}
Expand All @@ -38,24 +39,22 @@ private bool ConfiguredPathIsComplete()
return Path.StartsWith("Assets/") ||
Path.StartsWith("Packages/");
}

private bool TryGetPathFromComponent(UIComponent component, out string path)
private async Task<string> GetPathFromComponent(UIComponent component)
{
path = "";

foreach (var pathPart in component.GetAssetPaths())
{
var filePath = string.Join("/", pathPart, Path);

if (!component.AssetResolver.AssetExists(filePath))
var assetExists = await component.AssetResolver.AssetExists(filePath);

if (!assetExists)
continue;

path = filePath;

return true;
return filePath;
}

return false;
return null;
}
}
}
}
10 changes: 8 additions & 2 deletions Assets/UIComponents/Core/ResourcesAssetResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ public class ResourcesAssetResolver : IAssetResolver
return taskCompletionSource.Task;
}

public bool AssetExists(string assetPath)
public Task<bool> AssetExists(string assetPath)
{
return Resources.Load(assetPath) != null;
var request = Resources.LoadAsync(assetPath);

var taskCompletionSource = new TaskCompletionSource<bool>();

request.completed += operation => taskCompletionSource.SetResult(request.asset != null);

return taskCompletionSource.Task;
}
}
}
Loading

0 comments on commit 3dfe624

Please sign in to comment.