From cb0b1bee504cd018e9fedc59de65697e5b18dac5 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 5 Jul 2020 00:21:57 -0400 Subject: [PATCH 01/19] Add async FromMethod provider --- .../Zenject/OptionalExtras/Async/Runtime.meta | 8 ++ .../Runtime/AsyncDiContainerExtensions.cs | 26 ++++++ .../AsyncDiContainerExtensions.cs.meta | 3 + .../Async/Runtime/AsyncInject.cs | 83 +++++++++++++++++++ .../Async/Runtime/AsyncInject.cs.meta | 3 + .../OptionalExtras/Async/Runtime/Binders.meta | 3 + .../Runtime/Binders/AsyncFromBinderBase.cs | 27 ++++++ .../Binders/AsyncFromBinderBase.cs.meta | 3 + .../Runtime/Binders/AsyncFromBinderGeneric.cs | 39 +++++++++ .../Binders/AsyncFromBinderGeneric.cs.meta | 3 + .../Binders/ConcreteAsyncBinderGeneric.cs | 30 +++++++ .../ConcreteAsyncBinderGeneric.cs.meta | 3 + .../Binders/ConcreteAsyncIdBinderGeneric.cs | 19 +++++ .../ConcreteAsyncIdBinderGeneric.cs.meta | 3 + .../Async/Runtime/Extenject-Async.asmdef | 15 ++++ .../Async/Runtime/Extenject-Async.asmdef.meta | 7 ++ .../Async/Runtime/Providers.meta | 3 + .../Providers/AsyncMethodProviderSimple.cs | 64 ++++++++++++++ .../AsyncMethodProviderSimple.cs.meta | 3 + 19 files changed, 345 insertions(+) create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs.meta diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime.meta new file mode 100644 index 000000000..213c02997 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fc8c4aa98540d8d45914c45695c00cc1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs new file mode 100644 index 000000000..83c70a55f --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs @@ -0,0 +1,26 @@ +using ModestTree; + +namespace Zenject +{ + public static class AsyncDiContainerExtensions + { + public static ConcreteAsyncIdBinderGeneric BindAsync(this DiContainer container) + { + return BindAsync(container, container.StartBinding()); + } + + public static ConcreteAsyncIdBinderGeneric BindAsync(this DiContainer container, BindStatement bindStatement) + { + var bindInfo = bindStatement.SpawnBindInfo(); + + Assert.That(!typeof(TContract).DerivesFrom(), + "You should not use Container.BindAsync for factory classes. Use Container.BindFactory instead."); + + Assert.That(!bindInfo.ContractTypes.Contains(typeof(AsyncInject))); + bindInfo.ContractTypes.Add(typeof(AsyncInject)); + + return new ConcreteAsyncIdBinderGeneric( + container, bindInfo, bindStatement); + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs.meta new file mode 100644 index 000000000..beb735e49 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 552755346c0745d7bd238b074b30353d +timeCreated: 1593918171 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs new file mode 100644 index 000000000..8b470e82e --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs @@ -0,0 +1,83 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ModestTree; + +namespace Zenject +{ + [ZenjectAllowDuringValidation] + [NoReflectionBaking] + public class AsyncInject + { + readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + readonly InjectContext _context; + + public event Action Completed; + public event Action Faulted; + public event Action Cancelled; + + public bool HasResult { get; private set; } + public bool IsCancelled { get; private set; } + public bool IsFaulted { get; private set; } + T _result; + + public AsyncInject(InjectContext context, Func> asyncMethod) + { + _context = context; + + StartAsync(asyncMethod, cancellationTokenSource.Token); + } + + + public void Cancel() + { + cancellationTokenSource.Cancel(); + } + + private async void StartAsync(Func> asyncMethod, CancellationToken token) + { + Task task = asyncMethod(token); + await task; + + if (token.IsCancellationRequested) + { + return; + } + + if (task.IsCompleted) + { + _result = task.Result; + HasResult = true; + Completed?.Invoke(task.Result); + }else if (task.IsCanceled) + { + IsCancelled = true; + Cancelled?.Invoke(); + }else if (task.IsFaulted) + { + IsFaulted = true; + Faulted?.Invoke(task.Exception); + } + } + + public bool TryGetResult(out T result) + { + if (HasResult) + { + result = _result; + return true; + } + result = default; + return false; + } + + public T Result + { + get + { + Assert.That(HasResult, "AsyncInject does not have a result. "); + return _result; + } + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs.meta new file mode 100644 index 000000000..5c6580985 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ccd159e3fca64594aed21b2c23728200 +timeCreated: 1593623842 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders.meta new file mode 100644 index 000000000..81a4f2fd7 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ea94649cc87248d6b7c83152035c58ef +timeCreated: 1593628209 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs new file mode 100644 index 000000000..4dad5c65f --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs @@ -0,0 +1,27 @@ +using System; +using System.Diagnostics.Contracts; + +namespace Zenject +{ + [NoReflectionBaking] + public class AsyncFromBinderBase : ScopeConcreteIdArgConditionCopyNonLazyBinder + { + public AsyncFromBinderBase(DiContainer bindContainer, Type contractType, BindInfo bindInfo) + : base(bindInfo) + { + BindContainer = bindContainer; + ContractType = contractType; + } + + internal DiContainer BindContainer + { + get; private set; + } + + protected Type ContractType + { + get; private set; + } + + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs.meta new file mode 100644 index 000000000..43f8530c3 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: afefb93ca4194de4a97fe68d8ce948e4 +timeCreated: 1593628300 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs new file mode 100644 index 000000000..e2835cfa8 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; + +namespace Zenject +{ + public class AsyncFromBinderGeneric : AsyncFromBinderBase where TConcrete : TContract + { + public AsyncFromBinderGeneric( + DiContainer container, BindInfo bindInfo, + BindStatement bindStatement) + : base(container, typeof(TContract), bindInfo) + { + BindStatement = bindStatement; + } + + protected BindStatement BindStatement + { + get; private set; + } + + protected IBindingFinalizer SubFinalizer + { + set { BindStatement.SetFinalizer(value); } + } + + public AsyncFromBinderBase FromMethod(Func> method) + { + BindInfo.RequireExplicitScope = false; + // Don't know how it's created so can't assume here that it violates AsSingle + BindInfo.MarkAsCreationBinding = false; + SubFinalizer = new ScopableBindingFinalizer( + BindInfo, + (container, originalType) => new AsyncMethodProviderSimple(method)); + + return this; + } + + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs.meta new file mode 100644 index 000000000..d76b01e99 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a90ebf554b2241dd89c69883e3bd399e +timeCreated: 1593628851 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs new file mode 100644 index 000000000..e6f8bbaca --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModestTree; + +namespace Zenject +{ + public class ConcreteAsyncBinderGeneric : AsyncFromBinderGeneric + { + public ConcreteAsyncBinderGeneric( + DiContainer bindContainer, BindInfo bindInfo, + BindStatement bindStatement) + : base(bindContainer, bindInfo, bindStatement) + { + bindInfo.ToChoice = ToChoices.Self; + } + + public AsyncFromBinderGeneric To() + where TConcrete : TContract + { + /* + BindInfo.ToChoice = ToChoices.Concrete; + BindInfo.ToTypes.Clear(); + BindInfo.ToTypes.Add(typeof(AsyncInject)); + */ + return new AsyncFromBinderGeneric( + BindContainer, BindInfo, BindStatement); + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs.meta new file mode 100644 index 000000000..cbf761034 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 11dfa5bdf00747969d6004197d2b5474 +timeCreated: 1593631454 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs new file mode 100644 index 000000000..719aa6443 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs @@ -0,0 +1,19 @@ +namespace Zenject +{ + [NoReflectionBaking] + public class ConcreteAsyncIdBinderGeneric : ConcreteAsyncBinderGeneric + { + public ConcreteAsyncIdBinderGeneric( + DiContainer bindContainer, BindInfo bindInfo, + BindStatement bindStatement) + : base(bindContainer, bindInfo, bindStatement) + { + } + + public ConcreteAsyncBinderGeneric WithId(object identifier) + { + BindInfo.Identifier = identifier; + return this; + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs.meta new file mode 100644 index 000000000..9c5f9ce6f --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncIdBinderGeneric.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7e53ee42d6a14260b7836ce3b9121958 +timeCreated: 1593626138 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef new file mode 100644 index 000000000..527ace07a --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef @@ -0,0 +1,15 @@ +{ + "name": "Extenject-Async", + "references": [ + "Zenject" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef.meta new file mode 100644 index 000000000..99b329fd9 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9bf848f31e601a249a6bebdc53287470 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers.meta new file mode 100644 index 000000000..5401adf0f --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 07ce4df13cf04773a2251f822737f454 +timeCreated: 1593623009 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs new file mode 100644 index 000000000..6fa8209d3 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Threading; +using System.Threading.Tasks; +using ModestTree; + +namespace Zenject +{ + [NoReflectionBaking] + public class AsyncMethodProviderSimple : IProvider where TConcrete : TContract + { + readonly Func> _method; + readonly Func> _methodCancellable; + + public AsyncMethodProviderSimple(Func> method) + { + _method = method; + } + + public AsyncMethodProviderSimple(Func> method) + { + _methodCancellable = method; + } + + public bool TypeVariesBasedOnMemberType => false; + public bool IsCached => false; + public Type GetInstanceType(InjectContext context) => typeof(TConcrete); + + public void GetAllInstancesWithInjectSplit(InjectContext context, List args, out Action injectAction, List buffer) + { + Assert.IsEmpty(args); + Assert.IsNotNull(context); + + Assert.That(typeof(TConcrete).DerivesFromOrEqual(context.MemberType.GenericTypeArguments[0])); + + injectAction = null; + + Func> typeCastAsyncCall = null; + if (_methodCancellable != null) + { + typeCastAsyncCall = async ct => + { + var task = _methodCancellable(ct); + await task; + return (TContract) task.Result; + }; + }else if (_method != null) + { + typeCastAsyncCall = async _ => + { + var task = _method(); + await task; + return (TContract) task.Result; + }; + } + Assert.IsNotNull(typeCastAsyncCall); + + var asyncInject = new AsyncInject(context, typeCastAsyncCall); + + buffer.Add(asyncInject); + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs.meta new file mode 100644 index 000000000..6ac833e2c --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4d44f5c255f640aa8da6d4c74628f22f +timeCreated: 1593623062 \ No newline at end of file From 7ace7920931a7a1082e790d95897bde8857c653b Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 5 Jul 2020 00:23:14 -0400 Subject: [PATCH 02/19] Add async tests --- .../Zenject/OptionalExtras/Async/Tests.meta | 8 +++ .../Async/Tests/Addressable.zip | Bin 0 -> 1953 bytes .../Async/Tests/Addressable.zip.meta | 7 +++ .../OptionalExtras/Async/Tests/Async.meta | 3 ++ .../Async/Tests/Async/TestAsync.cs | 46 ++++++++++++++++++ .../Async/Tests/Async/TestAsync.cs.meta | 3 ++ .../Async/Tests/Extenject-Async-Tests.asmdef | 24 +++++++++ .../Tests/Extenject-Async-Tests.asmdef.meta | 7 +++ 8 files changed, 98 insertions(+) create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef.meta diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests.meta new file mode 100644 index 000000000..d18d50db8 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 97397223be0f3fb4b8288ac430e957ec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip new file mode 100644 index 0000000000000000000000000000000000000000..5f02228e1477a9c6a6b7c240ca76b482f9dba113 GIT binary patch literal 1953 zcmWIWW@Zs#00H(rPXfRUD8U1y9aB<@Qj3cdlX6n^1E4B77>)r|{&kOTUJX?71Bk`Y zR0gFcrue4nl~k1Y_BrM`Ir2Dv_qcQL5yOAJQ1RH%wOUmTXL{;Axg||biobfxIiEFW zlE=2sf2=edIqp3<(*NnSlm-8}AcboRYaN*{=3ig1dx~=5MGFV%vg)cmHZA8(cg)^k zzv4cRrFeRt^ODj-+t1b>Njhos@3B*e;p@g_a?YnGg(__>KNR_D{-1(ddD~_$dC>VJ zd$)d7yxY^=5x>}k&O5HwbU*JNvr?dZp4TU-bW`59X0eP2?;YIvBtTW(@n|#9Tl0We z8mIU4a#KqZV^8>U9WoGbcv$N{QTfWGStj0s2YdCVyjy-UMCqsIWKNMIyJv6R{e9n6 z_B|VeofvXF@arwd??>j!Py2K1`i|5j zmCKxs)!i%)_wBh@WccWFZWQ+=Nr|2e=A{brvx*q=1wP(SQ{eBE{?CALJ17!g744i? z&&0qW$)Z!APNYzU&o*HsC@3sNYzUP`dJu+H*XsGS z@%wD#^b6eiWp(8ZDm8IVjQKUZ?{n{caT5)`-)wgF&BBA#XDdY7IlQ(_U3f4g z^7xXTN8NL``JIprj(PM{m-lhu6UA=|{8~Gh`jW#u!dt&>E$Xo{KbgL;!efiPZ;YZ5nN><2(YARv(#p^;vABv{5=+ie=Moq3dy<%|6cEzD#t1t2*zpwl*oL zxo>u?XwjWfaMtUj@#g&8`2DrVRJXIubQU&k=HM=LtB%(w=et{M@v}+W;KynKHp`yO zhQ5Tx4GXq3`>ftLLnOs(!TjWJZ2l*HhzM_2k!M}gB>zaG@kvIFux?7`+xRtr~N+IaaHx&Z2eVJp9(C^I=AG3%bT-Cha*?~ zKc{9od7I-3r&FuGSuL}7pPy>^>F;C7tk>W2vX;klPT4ba<)mL9mp+US*llSwEoXsg z%-g#&<}aJ``02zMal8!yTcrL?mlEUWO??$6A+~ctqnrG;)JfK9<|&6?YQEXBQuar+ zyPUk;ji|=Wo2$B$cmjDH`0dI(?@9XP9$%5#c_wK~WXF#eA9-hR{Mp`KRim)ma?4!n z|LkepNgn&F_!phJ9{C?B+kM#aBw%?{E!QSslKl+C$^?@xB>VLlaxobSxc;_v?c4C; z-6~C27H(0UoFhqQKeZ?3D4k?+`S`BStm*EV$LD9;TlAOuD1Q{x+%2(XR><^6Yr;w| zpS`tiX(s#SlF7#eE=b&{yLe9g^S*W443C_x=5`Y& zDak!L2dmy>9I>~abcI9b2XBBkBa=M?u0mG@=xq=XfKVt!Fc*UYSd@`Lf?oRLm<1lPeHlBCf&;O& zoAn^bbN;9AW}|x!GhHIv*T916IZXR7JbCcjcd#Y<9y0cUEL~gs8EPpj8z_2NflvZi JeiyTXcmQbm{GI>+ literal 0 HcmV?d00001 diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip.meta new file mode 100644 index 000000000..bc329712c --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0882256425fc88d4da3fe38756cec6b4 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async.meta new file mode 100644 index 000000000..b92a35b41 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 008eb6ebb56c4b35ba2c89cd91b20186 +timeCreated: 1593627945 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs new file mode 100644 index 000000000..908ca9997 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Threading.Tasks; +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace Zenject.Tests.Bindings +{ + public class TestAsync : ZenjectIntegrationTestFixture + { + [UnityTest] + public IEnumerator TestSimpleMethod() + { + PreInstall(); + + Container.BindAsync().FromMethod(async () => + { + await Task.Delay(100); + return (IFoo) new Foo(); + }).AsCached(); + PostInstall(); + + var asycFoo = Container.Resolve>(); + while (!asycFoo.HasResult) + { + yield return null; + } + + if (asycFoo.TryGetResult(out IFoo fooAfterLoad)) + { + Assert.NotNull(fooAfterLoad); + yield break; + } + Assert.Fail(); + } + + public interface IFoo + { + + } + + public class Foo : IFoo + { + + } + } +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs.meta new file mode 100644 index 000000000..dddbcbfa9 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5f4f3bb942f34f42b8d797b8037ee7bd +timeCreated: 1593628006 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef new file mode 100644 index 000000000..2c6c88b80 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef @@ -0,0 +1,24 @@ +{ + "name": "Async.Tests", + "references": [ + "Zenject-TestFramework", + "Zenject", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Extenject-Async" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Zenject-usage.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef.meta new file mode 100644 index 000000000..b6db8bb5f --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5939cea733ea13b4cbb397e50020fbc3 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 921f28c0c2af408288f21903b8a208b7e3738df3 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sat, 11 Jul 2020 22:21:18 -0400 Subject: [PATCH 03/19] Add awaiter for AsyncInject --- .../Async/Runtime/AsyncInject.cs | 10 ++++-- .../Async/Tests/Async/TestAsync.cs | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs index 8b470e82e..e93f39e1a 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using ModestTree; @@ -19,8 +20,10 @@ public class AsyncInject public bool HasResult { get; private set; } public bool IsCancelled { get; private set; } public bool IsFaulted { get; private set; } + T _result; - + Task task; + public AsyncInject(InjectContext context, Func> asyncMethod) { _context = context; @@ -28,7 +31,6 @@ public AsyncInject(InjectContext context, Func> async StartAsync(asyncMethod, cancellationTokenSource.Token); } - public void Cancel() { cancellationTokenSource.Cancel(); @@ -36,7 +38,7 @@ public void Cancel() private async void StartAsync(Func> asyncMethod, CancellationToken token) { - Task task = asyncMethod(token); + task = asyncMethod(token); await task; if (token.IsCancellationRequested) @@ -79,5 +81,7 @@ public T Result return _result; } } + + public TaskAwaiter GetAwaiter() => task.GetAwaiter(); } } \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs index 908ca9997..96d2804e0 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs @@ -20,6 +20,7 @@ public IEnumerator TestSimpleMethod() PostInstall(); var asycFoo = Container.Resolve>(); + while (!asycFoo.HasResult) { yield return null; @@ -32,7 +33,38 @@ public IEnumerator TestSimpleMethod() } Assert.Fail(); } + + private IFoo awaitReturn; + [UnityTest] + [Timeout(300)] + public IEnumerator TestSimpleMethodAwaitable() + { + PreInstall(); + + Container.BindAsync().FromMethod(async () => + { + await Task.Delay(100); + return (IFoo) new Foo(); + }).AsCached(); + PostInstall(); + + var asycFoo = Container.Resolve>(); + + awaitReturn = null; + TestAwait(asycFoo); + + while (awaitReturn == null) + { + yield return null; + } + Assert.Pass(); + } + private async void TestAwait(AsyncInject asycFoo) + { + awaitReturn = await asycFoo; + } + public interface IFoo { From 1664704e131857dac7ce877eff8e99f6d155437e Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sat, 11 Jul 2020 22:24:46 -0400 Subject: [PATCH 04/19] Add documentation --- Documentation/Async.md | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 Documentation/Async.md diff --git a/Documentation/Async.md b/Documentation/Async.md new file mode 100644 index 000000000..ef0999878 --- /dev/null +++ b/Documentation/Async.md @@ -0,0 +1,89 @@ + +## Async Extensions *Experimental* + + +## Table Of Contents + +* Introduction + * Async in DI + * Example +* Advanced + * Static Memory Pools + * Using statements and dispose pattern + + + +## Introduction +### Async in DI + +In dependency injection, the injector resolves dependencies of the target class only once, often after class is first created. In other words, injection is a one time process that does not track the injected dependencies to update them later on. If a dependency is not ready at the moment of injection; either binding would not resolve in case of optional bindings or fail completely throwing an error. + +This is creates a dilemma when implementing dependencies that are resolved asyncroniously. You can design around the DI limitations by carefully architecting your code so that the injection happens after async process is completed. This requires careful planning, increased complexity in setup. It is also prone to errors. + +Alternatively you can inject a intermediary object that tracks the result of the async operation. When you need to access the dependency, you can use this intermediary object to check if async task is completed and get the resulting object. With the experimental async support, we would like to provide ways to tackle this problem in Extenject. You can find Async extensions in **Plugins/Zenject/OptionalExtras/Async** folder. + +### Example + +Lets see how we can inject async dependencies through an intermediary object. Async extensions implements `AsyncInject` as this intermediary. You can use it as follows. + + +```csharp +public class TestInstaller : MonoInstaller +{ + public override void InstallBindings() + { + Container.BindAsync().FromMethod(async () => + { + await Task.Delay(100); + return (IFoo) new Foo(); + }).AsCached(); + } +} + +public class Bar : IInitializable, IDisposable +{ + readonly AsyncInject _asyncFoo; + IFoo _foo; + public Bar(AsyncInject asyncFoo) + { + _asyncFoo = asyncFoo; + } + + public void Initialize() + { + if (!asycFoo.TryGetResult(out _foo)) + { + asycFoo.Completed += OnFooReady; + } + } + + private void OnFooReady(IFoo foo) + { + _foo = foo; + } + + public void Dispose() + { + asycFoo.Completed -= OnFooReady; + } +} +``` + +Here we use `BindAsync().FromMethod()` to pass an async method delegate that waits for 100 ms and then returns a newly created `Foo` object. This method can be any other method with `async Task Method()` signature. `BindAsync` extension provides a separate binder for async operations. This binder is currently limited to a few `FromX()` providers. Features like Pooling and Factories are not supported at the moment. + +With above binding `AsyncInject` object is added to the container. Since scope is set to `AsCached()` the operation will run only once and `AsyncInject` will keep the result. It is important to note that async operation will not start before this binding is resolved. If you want async operation to start immediately after installing use `NonLazy()` option. + +Once injected to `Bar`, we can check whether the return value of the async operation is already available by `TryGetResult`. method. This method will return false if there is no result to return. If result is not ready yet, we can listen to `Completed` event to get the return value when async operation completes. + +Alternatively we can use following methods to check result. +```csharp +// Use HasResult to check if result exists +if (asycFoo.HasResult) +{ + // Result will throw error if prematurely used. + var foo = asycFoo.Result; +} + +// AsyncInject provides custom awaiter +IFoo foo = await asyncFoo; +``` \ No newline at end of file From 791719c76ff2d9332f03356ae6f0ac0f6e5b9462 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sat, 18 Jul 2020 23:15:15 -0400 Subject: [PATCH 05/19] Allow typeless AsyncInject for list injections --- .../Runtime/AsyncDiContainerExtensions.cs | 3 ++- .../Async/Runtime/AsyncInject.cs | 23 +++++++++++++++---- .../Providers/AsyncMethodProviderSimple.cs | 4 +--- .../Async/Tests/Async/TestAsync.cs | 19 +++++++++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs index 83c70a55f..c437f7d42 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs @@ -17,8 +17,9 @@ public static ConcreteAsyncIdBinderGeneric BindAsync(this "You should not use Container.BindAsync for factory classes. Use Container.BindFactory instead."); Assert.That(!bindInfo.ContractTypes.Contains(typeof(AsyncInject))); + bindInfo.ContractTypes.Add(typeof(AsyncInject)); bindInfo.ContractTypes.Add(typeof(AsyncInject)); - + return new ConcreteAsyncIdBinderGeneric( container, bindInfo, bindStatement); } diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs index e93f39e1a..b9cfc2da6 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs @@ -6,9 +6,20 @@ namespace Zenject { + public interface AsyncInject + { + bool HasResult { get; } + bool IsCancelled { get; } + bool IsFaulted { get; } + bool IsCompleted { get; } + + TaskAwaiter GetAwaiter(); + } + + [ZenjectAllowDuringValidation] [NoReflectionBaking] - public class AsyncInject + public class AsyncInject : AsyncInject { readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); readonly InjectContext _context; @@ -17,9 +28,11 @@ public class AsyncInject public event Action Faulted; public event Action Cancelled; - public bool HasResult { get; private set; } - public bool IsCancelled { get; private set; } - public bool IsFaulted { get; private set; } + public bool HasResult { get; protected set; } + public bool IsCancelled { get; protected set; } + public bool IsFaulted { get; protected set; } + + public bool IsCompleted => HasResult || IsCancelled || IsFaulted; T _result; Task task; @@ -83,5 +96,7 @@ public T Result } public TaskAwaiter GetAwaiter() => task.GetAwaiter(); + + TaskAwaiter AsyncInject.GetAwaiter() => task.ContinueWith(task => { }).GetAwaiter(); } } \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs index 6fa8209d3..0274d0eca 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AsyncMethodProviderSimple.cs @@ -31,9 +31,7 @@ public void GetAllInstancesWithInjectSplit(InjectContext context, List> typeCastAsyncCall = null; diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs index 96d2804e0..f0a3b1b79 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs @@ -33,6 +33,25 @@ public IEnumerator TestSimpleMethod() } Assert.Fail(); } + + [UnityTest] + public IEnumerator TestUntypedInject() + { + PreInstall(); + + Container.BindAsync().FromMethod(async () => + { + await Task.Delay(100); + return (IFoo) new Foo(); + }).AsCached(); + PostInstall(); + + var asycFoo = Container.Resolve(); + yield return null; + + Assert.NotNull(asycFoo); + } + private IFoo awaitReturn; [UnityTest] From 89bcaf37a4f6f509d37f82e00c57b20ac2b0948e Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sat, 18 Jul 2020 23:15:47 -0400 Subject: [PATCH 06/19] Fix error in test resetting after async operation --- .../Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs index f0a3b1b79..c411e8468 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs @@ -67,9 +67,9 @@ public IEnumerator TestSimpleMethodAwaitable() }).AsCached(); PostInstall(); + awaitReturn = null; var asycFoo = Container.Resolve>(); - awaitReturn = null; TestAwait(asycFoo); while (awaitReturn == null) From 9af106d3dce4a4f64e9333113b5df6d33491cba9 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sat, 18 Jul 2020 23:16:35 -0400 Subject: [PATCH 07/19] Add preloading tests --- .../Async/Tests/Async/TestAsync.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs index c411e8468..2ff7b7227 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs @@ -1,6 +1,9 @@ using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using NUnit.Framework; +using UnityEngine; using UnityEngine.TestTools; namespace Zenject.Tests.Bindings @@ -78,6 +81,38 @@ public IEnumerator TestSimpleMethodAwaitable() } Assert.Pass(); } + + [UnityTest] + [Timeout(10500)] + public IEnumerator TestPreloading() + { + PreInstall(); + for (int i = 0; i < 4; i++) + { + var index = i; + Container.BindAsync().FromMethod(async () => + { + var delayAmount = 100 * (4 - index); + await Task.Delay(delayAmount); + return (IFoo) new Foo(); + }).AsCached(); + } + + Container.BindInterfacesTo().AsCached(); + Container.Decorate() + .With(); + + PostInstall(); + + float startTime = Time.timeSinceLevelLoad; + var testKernel = Container.Resolve() as PreloadAsyncKernel; + while (!testKernel.IsPreloadCompleted) + { + yield return null; + } + float deltaTime = Time.timeSinceLevelLoad - startTime; + Assert.GreaterOrEqual(deltaTime, 0.4f); + } private async void TestAwait(AsyncInject asycFoo) { @@ -93,5 +128,46 @@ public class Foo : IFoo { } + + public class PreloadAsyncKernel: BaseMonoKernelDecorator + { + [Inject] + private List asyncInjects; + + public bool IsPreloadCompleted { get; private set; } + + public async override void Initialize() + { + foreach (AsyncInject inject in asyncInjects) + { + if (!inject.IsCompleted) + { + await Task.Delay(1); + } + } + + IsPreloadCompleted = true; + DecoratedMonoKernel.Initialize(); + } + + public override void Update() + { + if (!IsPreloadCompleted) + { + return; + } + DecoratedMonoKernel.Update(); + } + + public override void FixedUpdate() + { + if (!IsPreloadCompleted) + { + + } + DecoratedMonoKernel.FixedUpdate(); + } + } } + } \ No newline at end of file From b70860dd56b1d4fdb0aafc3c92631a39cb1ca82b Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 19 Jul 2020 14:01:19 -0400 Subject: [PATCH 08/19] Removed addressable test from zip file Used version defines to add EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS define when Addressables package is added to the project --- .../Async/Runtime/Extenject-Async.asmdef | 12 +- ...{Addressable.zip.meta => Addressable.meta} | 3 +- .../Async/Tests/Addressable.zip | Bin 1953 -> 0 bytes .../Extenject-Addressable-Tests.asmdef | 31 +++++ .../Extenject-Addressable-Tests.asmdef.meta | 7 ++ .../Async/Tests/Addressable/ReadMe.txt | 3 + .../Async/Tests/Addressable/ReadMe.txt.meta | 7 ++ .../Tests/Addressable/TestAddressable.cs | 110 ++++++++++++++++++ .../Tests/Addressable/TestAddressable.cs.meta | 11 ++ .../Async/Tests/Extenject-Async-Tests.asmdef | 1 - 10 files changed, 181 insertions(+), 4 deletions(-) rename UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/{Addressable.zip.meta => Addressable.meta} (67%) delete mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs.meta diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef index 527ace07a..7c9a79716 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Extenject-Async.asmdef @@ -1,7 +1,9 @@ { "name": "Extenject-Async", "references": [ - "Zenject" + "GUID:0d8beb7f090555447a6cf5ce9e54dbb4", + "GUID:9e24947de15b9834991c9d8411ea37cf", + "GUID:84651a3751eca9349aac36a66bba901b" ], "includePlatforms": [], "excludePlatforms": [], @@ -10,6 +12,12 @@ "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], - "versionDefines": [], + "versionDefines": [ + { + "name": "com.unity.addressables", + "expression": "0.0.0", + "define": "EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS" + } + ], "noEngineReferences": false } \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.meta similarity index 67% rename from UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip.meta rename to UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.meta index bc329712c..47584d167 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip.meta +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.meta @@ -1,5 +1,6 @@ fileFormatVersion: 2 -guid: 0882256425fc88d4da3fe38756cec6b4 +guid: e74ebac8913e9334586de36dff73257d +folderAsset: yes DefaultImporter: externalObjects: {} userData: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable.zip deleted file mode 100644 index 5f02228e1477a9c6a6b7c240ca76b482f9dba113..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1953 zcmWIWW@Zs#00H(rPXfRUD8U1y9aB<@Qj3cdlX6n^1E4B77>)r|{&kOTUJX?71Bk`Y zR0gFcrue4nl~k1Y_BrM`Ir2Dv_qcQL5yOAJQ1RH%wOUmTXL{;Axg||biobfxIiEFW zlE=2sf2=edIqp3<(*NnSlm-8}AcboRYaN*{=3ig1dx~=5MGFV%vg)cmHZA8(cg)^k zzv4cRrFeRt^ODj-+t1b>Njhos@3B*e;p@g_a?YnGg(__>KNR_D{-1(ddD~_$dC>VJ zd$)d7yxY^=5x>}k&O5HwbU*JNvr?dZp4TU-bW`59X0eP2?;YIvBtTW(@n|#9Tl0We z8mIU4a#KqZV^8>U9WoGbcv$N{QTfWGStj0s2YdCVyjy-UMCqsIWKNMIyJv6R{e9n6 z_B|VeofvXF@arwd??>j!Py2K1`i|5j zmCKxs)!i%)_wBh@WccWFZWQ+=Nr|2e=A{brvx*q=1wP(SQ{eBE{?CALJ17!g744i? z&&0qW$)Z!APNYzU&o*HsC@3sNYzUP`dJu+H*XsGS z@%wD#^b6eiWp(8ZDm8IVjQKUZ?{n{caT5)`-)wgF&BBA#XDdY7IlQ(_U3f4g z^7xXTN8NL``JIprj(PM{m-lhu6UA=|{8~Gh`jW#u!dt&>E$Xo{KbgL;!efiPZ;YZ5nN><2(YARv(#p^;vABv{5=+ie=Moq3dy<%|6cEzD#t1t2*zpwl*oL zxo>u?XwjWfaMtUj@#g&8`2DrVRJXIubQU&k=HM=LtB%(w=et{M@v}+W;KynKHp`yO zhQ5Tx4GXq3`>ftLLnOs(!TjWJZ2l*HhzM_2k!M}gB>zaG@kvIFux?7`+xRtr~N+IaaHx&Z2eVJp9(C^I=AG3%bT-Cha*?~ zKc{9od7I-3r&FuGSuL}7pPy>^>F;C7tk>W2vX;klPT4ba<)mL9mp+US*llSwEoXsg z%-g#&<}aJ``02zMal8!yTcrL?mlEUWO??$6A+~ctqnrG;)JfK9<|&6?YQEXBQuar+ zyPUk;ji|=Wo2$B$cmjDH`0dI(?@9XP9$%5#c_wK~WXF#eA9-hR{Mp`KRim)ma?4!n z|LkepNgn&F_!phJ9{C?B+kM#aBw%?{E!QSslKl+C$^?@xB>VLlaxobSxc;_v?c4C; z-6~C27H(0UoFhqQKeZ?3D4k?+`S`BStm*EV$LD9;TlAOuD1Q{x+%2(XR><^6Yr;w| zpS`tiX(s#SlF7#eE=b&{yLe9g^S*W443C_x=5`Y& zDak!L2dmy>9I>~abcI9b2XBBkBa=M?u0mG@=xq=XfKVt!Fc*UYSd@`Lf?oRLm<1lPeHlBCf&;O& zoAn^bbN;9AW}|x!GhHIv*T916IZXR7JbCcjcd#Y<9y0cUEL~gs8EPpj8z_2NflvZi JeiyTXcmQbm{GI>+ diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef new file mode 100644 index 000000000..46474692d --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef @@ -0,0 +1,31 @@ +{ + "name": "Extenject-Addressable-Tests", + "references": [ + "GUID:0d8beb7f090555447a6cf5ce9e54dbb4", + "GUID:a2f2239355369ba4fb6909aeaa41def5", + "GUID:9bf848f31e601a249a6bebdc53287470", + "GUID:9e24947de15b9834991c9d8411ea37cf", + "GUID:84651a3751eca9349aac36a66bba901b", + "GUID:27619889b8ba8c24980f49ee34dbb44a" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Zenject-usage.dll" + ], + "autoReferenced": true, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [ + { + "name": "com.unity.scriptablebuildpipeline", + "expression": "0.0.0", + "define": "EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef.meta new file mode 100644 index 000000000..26aa90077 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/Extenject-Addressable-Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3c4567cf868de4741acbfbbf604db477 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt new file mode 100644 index 000000000..b6b92a5be --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt @@ -0,0 +1,3 @@ +Create a GameObject prefab and add it to addressable with id "TestAddressablePrefab". This id is used in test. Unfortunately we cannot distribute this with package. + +Add Addressables and ResourceManager references to the test assembly definition. \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt.meta new file mode 100644 index 000000000..564bd3b24 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/ReadMe.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d987748cb574f84419d80025765708da +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs new file mode 100644 index 000000000..62429cff4 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs @@ -0,0 +1,110 @@ +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS +using System; +using Zenject; +using System.Collections; +using System.Collections.Generic; +using ModestTree; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement; +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.ResourceManagement.ResourceLocations; +using Assert = NUnit.Framework.Assert; + +public class TestAddressable : ZenjectIntegrationTestFixture +{ + private AssetReferenceT addressablePrefabReference; + + [TearDown] + public void Teardown() + { + addressablePrefabReference = null; + Resources.UnloadUnusedAssets(); + } + + [UnityTest] + public IEnumerator TestAddressableAsyncLoad() + { + yield return ValidateTestDependency(); + + PreInstall(); + AsyncOperationHandle handle = default; + Container.BindAsync().FromMethod(async () => + { + try + { + var locationsHandle = Addressables.LoadResourceLocationsAsync("TestAddressablePrefab"); + await locationsHandle.Task; + Assert.Greater(locationsHandle.Result.Count, 0, "Key required for test is not configured. Check Readme.txt in addressable test folder"); + + IResourceLocation location = locationsHandle.Result[0]; + handle = Addressables.LoadAsset(location); + await handle.Task; + return handle.Result; + } + catch (InvalidKeyException) + { + + } + return null; + }).AsCached(); + PostInstall(); + + yield return null; + + AsyncInject asycFoo = Container.Resolve>(); + + int frameCounter = 0; + while (!asycFoo.HasResult && !asycFoo.IsFaulted) + { + frameCounter++; + if (frameCounter > 10000) + { + Addressables.Release(handle); + Assert.Fail(); + } + yield return null; + } + + Addressables.Release(handle); + Assert.Pass(); + } + + + private IEnumerator ValidateTestDependency() + { + AsyncOperationHandle> locationsHandle = default(AsyncOperationHandle>); + try + { + locationsHandle = Addressables.LoadResourceLocationsAsync("TestAddressablePrefab"); + } + catch (Exception e) + { + Assert.Inconclusive("You need to set TestAddressablePrefab key to run this test"); + yield break; + } + + while (!locationsHandle.IsDone) + { + yield return null; + } + + var locations = locationsHandle.Result; + if (locations == null || locations.Count == 0) + { + Assert.Inconclusive("You need to set TestAddressablePrefab key to run this test"); + } + + var resourceLocation = locations[0]; + + if (resourceLocation.ResourceType != typeof(GameObject)) + { + Assert.Inconclusive("TestAddressablePrefab should be a GameObject"); + } + + addressablePrefabReference = new AssetReferenceT(resourceLocation.PrimaryKey); + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs.meta new file mode 100644 index 000000000..527ba0420 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0467565a36ec4624caee4cedb95898b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef index 2c6c88b80..aa53c0918 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Extenject-Async-Tests.asmdef @@ -4,7 +4,6 @@ "Zenject-TestFramework", "Zenject", "UnityEngine.TestRunner", - "UnityEditor.TestRunner", "Extenject-Async" ], "includePlatforms": [], From 9de197daa374b87f92b710f346fac9026116bf65 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 19 Jul 2020 13:57:40 -0400 Subject: [PATCH 09/19] Add naive implementation for AssetReferenceT --- .../Runtime/Binders/AsyncFromBinderGeneric.cs | 24 ++++++++++++++++ .../Tests/Addressable/TestAddressable.cs | 28 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs index e2835cfa8..8e5722d90 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs @@ -1,5 +1,9 @@ using System; using System.Threading.Tasks; +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.AddressableAssets; +#endif namespace Zenject { @@ -35,5 +39,25 @@ public AsyncFromBinderBase FromMethod(Func> method) return this; } +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS + public AsyncFromBinderBase FromAssetReferenceT(AssetReferenceT reference) where TConcreteO:UnityEngine.Object, TConcrete + { + Func> addressableLoadDelegate = async () => + { + AsyncOperationHandle loadHandle = Addressables.LoadAssetAsync(reference); + await loadHandle.Task; + return loadHandle.Result; + }; + + BindInfo.RequireExplicitScope = false; + // Don't know how it's created so can't assume here that it violates AsSingle + BindInfo.MarkAsCreationBinding = false; + SubFinalizer = new ScopableBindingFinalizer( + BindInfo, + (container, originalType) => new AsyncMethodProviderSimple(addressableLoadDelegate)); + + return this; + } +#endif } } \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs index 62429cff4..cc87a3b75 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs @@ -72,6 +72,34 @@ public IEnumerator TestAddressableAsyncLoad() Assert.Pass(); } + [UnityTest] + public IEnumerator TestAssetReferenceTMethod() + { + yield return ValidateTestDependency(); + + PreInstall(); + + Container.BindAsync() + .FromAssetReferenceT(addressablePrefabReference) + .AsCached(); + PostInstall(); + + AsyncInject asycFoo = Container.Resolve>(); + + int frameCounter = 0; + while (!asycFoo.HasResult && !asycFoo.IsFaulted) + { + frameCounter++; + if (frameCounter > 10000) + { + Assert.Fail(); + } + yield return null; + } + + Addressables.Release(asycFoo.Result); + Assert.Pass(); + } private IEnumerator ValidateTestDependency() { From 3efbebee36ebd58634e12d250a5e9a4daed58a8b Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 19 Jul 2020 16:07:32 -0400 Subject: [PATCH 10/19] Create addressable binder for type safe binding --- .../Runtime/AsyncDiContainerExtensions.cs | 25 +++++++++--- .../Binders/AddressableFromBinderGeneric.cs | 40 +++++++++++++++++++ .../AddressableFromBinderGeneric.cs.meta | 3 ++ .../Runtime/Binders/AsyncFromBinderGeneric.cs | 22 +--------- .../ConcreteAddressableBinderGeneric.cs | 23 +++++++++++ .../ConcreteAddressableBinderGeneric.cs.meta | 3 ++ .../ConcreteAddressableIdBinderGeneric.cs | 22 ++++++++++ ...ConcreteAddressableIdBinderGeneric.cs.meta | 3 ++ .../Binders/ConcreteAsyncBinderGeneric.cs | 6 +-- 9 files changed, 116 insertions(+), 31 deletions(-) create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs.meta create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs.meta diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs index c437f7d42..b96f6c559 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs @@ -4,12 +4,24 @@ namespace Zenject { public static class AsyncDiContainerExtensions { - public static ConcreteAsyncIdBinderGeneric BindAsync(this DiContainer container) + public static +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS + ConcreteAddressableIdBinderGeneric +#else + ConcreteAsyncIdBinderGeneric +#endif + BindAsync(this DiContainer container) { return BindAsync(container, container.StartBinding()); } - public static ConcreteAsyncIdBinderGeneric BindAsync(this DiContainer container, BindStatement bindStatement) + public static +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS + ConcreteAddressableIdBinderGeneric +#else + ConcreteAsyncIdBinderGeneric +#endif + BindAsync(this DiContainer container, BindStatement bindStatement) { var bindInfo = bindStatement.SpawnBindInfo(); @@ -19,9 +31,12 @@ public static ConcreteAsyncIdBinderGeneric BindAsync(this Assert.That(!bindInfo.ContractTypes.Contains(typeof(AsyncInject))); bindInfo.ContractTypes.Add(typeof(AsyncInject)); bindInfo.ContractTypes.Add(typeof(AsyncInject)); - - return new ConcreteAsyncIdBinderGeneric( - container, bindInfo, bindStatement); + +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS + return new ConcreteAddressableIdBinderGeneric(container, bindInfo, bindStatement); +#else + return new ConcreteAsyncIdBinderGeneric(container, bindInfo, bindStatement); +#endif } } } \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs new file mode 100644 index 000000000..f6c05f160 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs @@ -0,0 +1,40 @@ +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS +using System; +using System.Threading.Tasks; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; + +namespace Zenject +{ + [NoReflectionBaking] + public class AddressableFromBinderGeneric : AsyncFromBinderGeneric + where TConcrete : TContract + { + public AddressableFromBinderGeneric( + DiContainer container, BindInfo bindInfo, + BindStatement bindStatement) + : base(container, bindInfo, bindStatement) + {} + + public AsyncFromBinderBase FromAssetReferenceT(AssetReferenceT reference) where TConcreteObj:UnityEngine.Object, TConcrete + { + Func> addressableLoadDelegate = async () => + { + AsyncOperationHandle loadHandle = Addressables.LoadAssetAsync(reference); + await loadHandle.Task; + return loadHandle.Result; + }; + + BindInfo.RequireExplicitScope = false; + // Don't know how it's created so can't assume here that it violates AsSingle + BindInfo.MarkAsCreationBinding = false; + SubFinalizer = new ScopableBindingFinalizer( + BindInfo, + (container, originalType) => new AsyncMethodProviderSimple(addressableLoadDelegate)); + + return this; + } + + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs.meta new file mode 100644 index 000000000..4960a1fa4 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4617aa76923941fca100087c6a1c0f47 +timeCreated: 1595185267 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs index 8e5722d90..b7fe5710c 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs @@ -7,6 +7,7 @@ namespace Zenject { + [NoReflectionBaking] public class AsyncFromBinderGeneric : AsyncFromBinderBase where TConcrete : TContract { public AsyncFromBinderGeneric( @@ -38,26 +39,5 @@ public AsyncFromBinderBase FromMethod(Func> method) return this; } - -#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS - public AsyncFromBinderBase FromAssetReferenceT(AssetReferenceT reference) where TConcreteO:UnityEngine.Object, TConcrete - { - Func> addressableLoadDelegate = async () => - { - AsyncOperationHandle loadHandle = Addressables.LoadAssetAsync(reference); - await loadHandle.Task; - return loadHandle.Result; - }; - - BindInfo.RequireExplicitScope = false; - // Don't know how it's created so can't assume here that it violates AsSingle - BindInfo.MarkAsCreationBinding = false; - SubFinalizer = new ScopableBindingFinalizer( - BindInfo, - (container, originalType) => new AsyncMethodProviderSimple(addressableLoadDelegate)); - - return this; - } -#endif } } \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs new file mode 100644 index 000000000..3d167639b --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs @@ -0,0 +1,23 @@ +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS +namespace Zenject +{ + [NoReflectionBaking] + public class ConcreteAddressableBinderGeneric : AddressableFromBinderGeneric + { + public ConcreteAddressableBinderGeneric( + DiContainer bindContainer, BindInfo bindInfo, + BindStatement bindStatement) + : base(bindContainer, bindInfo, bindStatement) + { + bindInfo.ToChoice = ToChoices.Self; + } + + public AddressableFromBinderGeneric To() + where TConcrete : TContract + { + return new AddressableFromBinderGeneric( + BindContainer, BindInfo, BindStatement); + } + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs.meta new file mode 100644 index 000000000..c3aeb0f01 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableBinderGeneric.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8f00f9577e78451f9c5bba42164bbbe6 +timeCreated: 1595185142 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs new file mode 100644 index 000000000..f1ce025ed --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs @@ -0,0 +1,22 @@ +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS + +namespace Zenject +{ + [NoReflectionBaking] + public class ConcreteAddressableIdBinderGeneric : ConcreteAddressableBinderGeneric + { + public ConcreteAddressableIdBinderGeneric( + DiContainer bindContainer, BindInfo bindInfo, + BindStatement bindStatement) + : base(bindContainer, bindInfo, bindStatement) + {} + + public ConcreteAddressableIdBinderGeneric WithId(object identifier) + { + BindInfo.Identifier = identifier; + return this; + } + + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs.meta new file mode 100644 index 000000000..f812811c4 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAddressableIdBinderGeneric.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6b8a4f8f2d7d4987bf374adc67374d87 +timeCreated: 1595184977 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs index e6f8bbaca..cd7d1df84 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/ConcreteAsyncBinderGeneric.cs @@ -5,6 +5,7 @@ namespace Zenject { + [NoReflectionBaking] public class ConcreteAsyncBinderGeneric : AsyncFromBinderGeneric { public ConcreteAsyncBinderGeneric( @@ -18,11 +19,6 @@ public ConcreteAsyncBinderGeneric( public AsyncFromBinderGeneric To() where TConcrete : TContract { - /* - BindInfo.ToChoice = ToChoices.Concrete; - BindInfo.ToTypes.Clear(); - BindInfo.ToTypes.Add(typeof(AsyncInject)); - */ return new AsyncFromBinderGeneric( BindContainer, BindInfo, BindStatement); } From a81a434db14df7d94a5c3675a7c2ab9741d4702a Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 19 Jul 2020 22:01:45 -0400 Subject: [PATCH 11/19] Add cancel token variant for FromMethod --- .../Runtime/Binders/AsyncFromBinderGeneric.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs index b7fe5710c..27818ff1a 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AsyncFromBinderGeneric.cs @@ -1,9 +1,6 @@ using System; +using System.Threading; using System.Threading.Tasks; -#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS -using UnityEngine.ResourceManagement.AsyncOperations; -using UnityEngine.AddressableAssets; -#endif namespace Zenject { @@ -39,5 +36,18 @@ public AsyncFromBinderBase FromMethod(Func> method) return this; } + + public AsyncFromBinderBase FromMethod(Func> method) + { + BindInfo.RequireExplicitScope = false; + // Don't know how it's created so can't assume here that it violates AsSingle + BindInfo.MarkAsCreationBinding = false; + SubFinalizer = new ScopableBindingFinalizer( + BindInfo, + (container, originalType) => new AsyncMethodProviderSimple(method)); + + return this; + } + } } \ No newline at end of file From 798721d4790390ab67f9b9db18bc4fee01c6fbe9 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 19 Jul 2020 22:02:45 -0400 Subject: [PATCH 12/19] Add AddressableInject variant for AsyncInject --- .../Async/Runtime/AddressableInject.cs | 25 +++++++++++++++++++ .../Async/Runtime/AddressableInject.cs.meta | 3 +++ .../Async/Runtime/AsyncInject.cs | 11 +++++--- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs.meta diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs new file mode 100644 index 000000000..51773f9e2 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs @@ -0,0 +1,25 @@ +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS +using System; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine.ResourceManagement.AsyncOperations; +using Zenject; + +[ZenjectAllowDuringValidation] +[NoReflectionBaking] +public class AddressableInject : AsyncInject where T : UnityEngine.Object +{ + private AsyncOperationHandle _handle; + public AsyncOperationHandle AssetReferenceHandle => _handle; + + public AddressableInject(InjectContext context, Func>> asyncMethod) + : base(context) + { + StartAsync(async (ct) => + { + _handle = await asyncMethod(ct); + return _handle.Result; + }, cancellationTokenSource.Token); + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs.meta new file mode 100644 index 000000000..d02f93e0b --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AddressableInject.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6a6d9c621b22426bb466edc56d2c97bb +timeCreated: 1595196971 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs index b9cfc2da6..a1441d049 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs @@ -21,8 +21,8 @@ public interface AsyncInject [NoReflectionBaking] public class AsyncInject : AsyncInject { - readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - readonly InjectContext _context; + protected readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + protected readonly InjectContext _context; public event Action Completed; public event Action Faulted; @@ -37,6 +37,11 @@ public class AsyncInject : AsyncInject T _result; Task task; + protected AsyncInject(InjectContext context) + { + _context = context; + } + public AsyncInject(InjectContext context, Func> asyncMethod) { _context = context; @@ -49,7 +54,7 @@ public void Cancel() cancellationTokenSource.Cancel(); } - private async void StartAsync(Func> asyncMethod, CancellationToken token) + protected async void StartAsync(Func> asyncMethod, CancellationToken token) { task = asyncMethod(token); await task; From c64f8a1c5b3a9ee3307a444ccc0d44790349c860 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 19 Jul 2020 22:08:29 -0400 Subject: [PATCH 13/19] Refactor FromAssetReferenceT to use AddressableInject --- .../Binders/AddressableFromBinderGeneric.cs | 21 +++++---- .../Providers/AddressableProviderSimple.cs | 46 +++++++++++++++++++ .../AddressableProviderSimple.cs.meta | 3 ++ .../Tests/Addressable/TestAddressable.cs | 8 ++-- 4 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs create mode 100644 UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs.meta diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs index f6c05f160..7f2a8905a 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Binders/AddressableFromBinderGeneric.cs @@ -2,6 +2,7 @@ using System; using System.Threading.Tasks; using UnityEngine.AddressableAssets; +using UnityEngine.Assertions; using UnityEngine.ResourceManagement.AsyncOperations; namespace Zenject @@ -16,21 +17,23 @@ public AddressableFromBinderGeneric( : base(container, bindInfo, bindStatement) {} - public AsyncFromBinderBase FromAssetReferenceT(AssetReferenceT reference) where TConcreteObj:UnityEngine.Object, TConcrete + public AsyncFromBinderBase FromAssetReferenceT(AssetReferenceT reference) + where TConcreteObj:UnityEngine.Object, TConcrete { - Func> addressableLoadDelegate = async () => - { - AsyncOperationHandle loadHandle = Addressables.LoadAssetAsync(reference); - await loadHandle.Task; - return loadHandle.Result; - }; - BindInfo.RequireExplicitScope = false; + + var contractType = typeof(TContract); + if (typeof(UnityEngine.Object).IsAssignableFrom(contractType)) + { + var addressableInjectType = typeof(AddressableInject<>).MakeGenericType(typeof(TContract)); + BindInfo.ContractTypes.Add(addressableInjectType); + } + // Don't know how it's created so can't assume here that it violates AsSingle BindInfo.MarkAsCreationBinding = false; SubFinalizer = new ScopableBindingFinalizer( BindInfo, - (container, originalType) => new AsyncMethodProviderSimple(addressableLoadDelegate)); + (container, originalType) => new AddressableProviderSimple(reference)); return this; } diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs new file mode 100644 index 000000000..b577d6212 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs @@ -0,0 +1,46 @@ +#if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using ModestTree; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; + +namespace Zenject +{ + [NoReflectionBaking] + public class AddressableProviderSimple : IProvider where TConcrete : UnityEngine.Object, TContract + { + private AssetReferenceT assetReference; + + public AddressableProviderSimple(AssetReferenceT assetReference) + { + this.assetReference = assetReference; + } + + public bool TypeVariesBasedOnMemberType => false; + public bool IsCached => false; + public Type GetInstanceType(InjectContext context) => typeof(TConcrete); + + public void GetAllInstancesWithInjectSplit(InjectContext context, List args, out Action injectAction, List buffer) + { + Assert.IsEmpty(args); + Assert.IsNotNull(context); + + injectAction = null; + + Func>> addressableLoadDelegate = async (_) => + { + AsyncOperationHandle loadHandle = Addressables.LoadAssetAsync(assetReference); + await loadHandle.Task; + return loadHandle; + }; + + var asyncInject = new AddressableInject(context, addressableLoadDelegate); + + buffer.Add(asyncInject); + } + } +} +#endif \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs.meta b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs.meta new file mode 100644 index 000000000..e9efac348 --- /dev/null +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a46dbe0d4a1445c3bb8c5e58739a07a3 +timeCreated: 1595192404 \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs index cc87a3b75..78df3dc58 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs @@ -84,10 +84,10 @@ public IEnumerator TestAssetReferenceTMethod() .AsCached(); PostInstall(); - AsyncInject asycFoo = Container.Resolve>(); + AddressableInject asyncPrefab = Container.Resolve>(); int frameCounter = 0; - while (!asycFoo.HasResult && !asycFoo.IsFaulted) + while (!asyncPrefab.HasResult && !asyncPrefab.IsFaulted) { frameCounter++; if (frameCounter > 10000) @@ -96,8 +96,8 @@ public IEnumerator TestAssetReferenceTMethod() } yield return null; } - - Addressables.Release(asycFoo.Result); + + Addressables.Release(asyncPrefab.AssetReferenceHandle); Assert.Pass(); } From e3114baab45769c007ce232a51e661e6d63e148d Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sun, 19 Jul 2020 22:15:07 -0400 Subject: [PATCH 14/19] Make preloading test non time dependent --- .../OptionalExtras/Async/Tests/Async/TestAsync.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs index 2ff7b7227..00a67ead7 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs @@ -104,14 +104,21 @@ public IEnumerator TestPreloading() PostInstall(); - float startTime = Time.timeSinceLevelLoad; var testKernel = Container.Resolve() as PreloadAsyncKernel; while (!testKernel.IsPreloadCompleted) { yield return null; } - float deltaTime = Time.timeSinceLevelLoad - startTime; - Assert.GreaterOrEqual(deltaTime, 0.4f); + + foreach (var asycFooUntyped in testKernel.asyncInjects) + { + var asycFoo = asycFooUntyped as AsyncInject; + if (asycFoo.TryGetResult(out IFoo fooAfterLoad)) + { + Assert.NotNull(fooAfterLoad); + yield break; + } + } } private async void TestAwait(AsyncInject asycFoo) @@ -132,7 +139,7 @@ public class Foo : IFoo public class PreloadAsyncKernel: BaseMonoKernelDecorator { [Inject] - private List asyncInjects; + public List asyncInjects; public bool IsPreloadCompleted { get; private set; } From b814fac38dc6e629ea685708fd78df8d60a851c0 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sat, 22 Aug 2020 19:14:46 -0400 Subject: [PATCH 15/19] Added failure check for async tasks --- .../Async/Runtime/AsyncInject.cs | 53 +++++++++++++++---- .../Tests/Addressable/TestAddressable.cs | 42 ++++++++++++++- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs index a1441d049..8e81252b0 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs @@ -29,10 +29,11 @@ public class AsyncInject : AsyncInject public event Action Cancelled; public bool HasResult { get; protected set; } + public bool IsSuccessful { get; protected set; } public bool IsCancelled { get; protected set; } public bool IsFaulted { get; protected set; } - public bool IsCompleted => HasResult || IsCancelled || IsFaulted; + public bool IsCompleted => IsSuccessful || IsCancelled || IsFaulted; T _result; Task task; @@ -56,30 +57,60 @@ public void Cancel() protected async void StartAsync(Func> asyncMethod, CancellationToken token) { - task = asyncMethod(token); - await task; - + try + { + task = asyncMethod(token); + await task; + } + catch (AggregateException e) + { + HandleFaulted(e); + return; + } + catch (Exception e) + { + HandleFaulted(new AggregateException(e)); + return; + } + if (token.IsCancellationRequested) { + HandleCancelled(); return; } if (task.IsCompleted) { - _result = task.Result; - HasResult = true; - Completed?.Invoke(task.Result); + HandleCompleted(task.Result); }else if (task.IsCanceled) { - IsCancelled = true; - Cancelled?.Invoke(); + HandleCancelled(); }else if (task.IsFaulted) { - IsFaulted = true; - Faulted?.Invoke(task.Exception); + HandleFaulted(task.Exception); } } + private void HandleCompleted(T result) + { + _result = result; + HasResult = !result.Equals(default(T)); + IsSuccessful = true; + Completed?.Invoke(result); + } + + private void HandleCancelled() + { + IsCancelled = true; + Cancelled?.Invoke(); + } + + private void HandleFaulted(AggregateException exception) + { + IsFaulted = true; + Faulted?.Invoke(exception); + } + public bool TryGetResult(out T result) { if (HasResult) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs index 78df3dc58..d3a3901d6 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Addressable/TestAddressable.cs @@ -3,12 +3,10 @@ using Zenject; using System.Collections; using System.Collections.Generic; -using ModestTree; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; using UnityEngine.AddressableAssets; -using UnityEngine.ResourceManagement; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using Assert = NUnit.Framework.Assert; @@ -100,6 +98,46 @@ public IEnumerator TestAssetReferenceTMethod() Addressables.Release(asyncPrefab.AssetReferenceHandle); Assert.Pass(); } + + [UnityTest] + [Timeout(10500)] + public IEnumerator TestFailedLoad() + { + PreInstall(); + + Container.BindAsync().FromMethod(async () => + { + FailedOperation failingOperation = new FailedOperation(); + var customHandle = Addressables.ResourceManager.StartOperation(failingOperation, default(AsyncOperationHandle)); + await customHandle.Task; + + if (customHandle.Status == AsyncOperationStatus.Failed) + { + throw new Exception("Async operation failed", customHandle.OperationException); + } + + return customHandle.Result; + }).AsCached(); + PostInstall(); + + yield return new WaitForEndOfFrame(); + + LogAssert.ignoreFailingMessages = true; + AsyncInject asyncGameObj = Container.Resolve>(); + LogAssert.ignoreFailingMessages = false; + + Assert.IsFalse(asyncGameObj.HasResult); + Assert.IsTrue(asyncGameObj.IsCompleted); + Assert.IsTrue(asyncGameObj.IsFaulted); + } + + private class FailedOperation : AsyncOperationBase + { + protected override void Execute() + { + Complete(null, false, "Intentionally failed message"); + } + } private IEnumerator ValidateTestDependency() { From c20caac2e39b3f92dea6677f4df115f034131a2e Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Sat, 22 Aug 2020 19:16:25 -0400 Subject: [PATCH 16/19] Intentionally throw error on addressable fail --- .../Async/Runtime/Providers/AddressableProviderSimple.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs index b577d6212..9522258de 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/Providers/AddressableProviderSimple.cs @@ -34,6 +34,12 @@ public void GetAllInstancesWithInjectSplit(InjectContext context, List loadHandle = Addressables.LoadAssetAsync(assetReference); await loadHandle.Task; + + if (loadHandle.Status == AsyncOperationStatus.Failed) + { + throw new Exception("Async operation failed", loadHandle.OperationException); + } + return loadHandle; }; From b2708af781c67b5df74da0a9524a3c0b3a33472c Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Fri, 1 Jan 2021 12:54:09 -0500 Subject: [PATCH 17/19] Renamed AsyncInject to IAsyncInject --- .../Async/Runtime/AsyncDiContainerExtensions.cs | 2 +- .../Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs | 6 +++--- .../Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs index b96f6c559..cf22a7be3 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncDiContainerExtensions.cs @@ -29,7 +29,7 @@ public static "You should not use Container.BindAsync for factory classes. Use Container.BindFactory instead."); Assert.That(!bindInfo.ContractTypes.Contains(typeof(AsyncInject))); - bindInfo.ContractTypes.Add(typeof(AsyncInject)); + bindInfo.ContractTypes.Add(typeof(IAsyncInject)); bindInfo.ContractTypes.Add(typeof(AsyncInject)); #if EXTENJECT_INCLUDE_ADDRESSABLE_BINDINGS diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs index 8e81252b0..e1f598eed 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Runtime/AsyncInject.cs @@ -6,7 +6,7 @@ namespace Zenject { - public interface AsyncInject + public interface IAsyncInject { bool HasResult { get; } bool IsCancelled { get; } @@ -19,7 +19,7 @@ public interface AsyncInject [ZenjectAllowDuringValidation] [NoReflectionBaking] - public class AsyncInject : AsyncInject + public class AsyncInject : IAsyncInject { protected readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); protected readonly InjectContext _context; @@ -133,6 +133,6 @@ public T Result public TaskAwaiter GetAwaiter() => task.GetAwaiter(); - TaskAwaiter AsyncInject.GetAwaiter() => task.ContinueWith(task => { }).GetAwaiter(); + TaskAwaiter IAsyncInject.GetAwaiter() => task.ContinueWith(task => { }).GetAwaiter(); } } \ No newline at end of file diff --git a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs index 00a67ead7..fc937c54e 100644 --- a/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs +++ b/UnityProject/Assets/Plugins/Zenject/OptionalExtras/Async/Tests/Async/TestAsync.cs @@ -49,7 +49,7 @@ public IEnumerator TestUntypedInject() }).AsCached(); PostInstall(); - var asycFoo = Container.Resolve(); + var asycFoo = Container.Resolve(); yield return null; Assert.NotNull(asycFoo); @@ -139,13 +139,13 @@ public class Foo : IFoo public class PreloadAsyncKernel: BaseMonoKernelDecorator { [Inject] - public List asyncInjects; + public List asyncInjects; public bool IsPreloadCompleted { get; private set; } public async override void Initialize() { - foreach (AsyncInject inject in asyncInjects) + foreach (IAsyncInject inject in asyncInjects) { if (!inject.IsCompleted) { From a88111f786655b97a070a3ab9275221ae8af6d12 Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Fri, 1 Jan 2021 13:01:12 -0500 Subject: [PATCH 18/19] Update _asyncFoo variable naming in documentation --- Documentation/Async.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/Async.md b/Documentation/Async.md index ef0999878..84d39e61b 100644 --- a/Documentation/Async.md +++ b/Documentation/Async.md @@ -51,9 +51,9 @@ public class Bar : IInitializable, IDisposable public void Initialize() { - if (!asycFoo.TryGetResult(out _foo)) + if (!_asyncFoo.TryGetResult(out _foo)) { - asycFoo.Completed += OnFooReady; + _asyncFoo.Completed += OnFooReady; } } @@ -64,7 +64,7 @@ public class Bar : IInitializable, IDisposable public void Dispose() { - asycFoo.Completed -= OnFooReady; + _asyncFoo.Completed -= OnFooReady; } } ``` @@ -78,12 +78,12 @@ Once injected to `Bar`, we can check whether the return value of the async opera Alternatively we can use following methods to check result. ```csharp // Use HasResult to check if result exists -if (asycFoo.HasResult) +if (_asyncFoo.HasResult) { // Result will throw error if prematurely used. - var foo = asycFoo.Result; + var foo = _asyncFoo.Result; } // AsyncInject provides custom awaiter -IFoo foo = await asyncFoo; +IFoo foo = await _asyncFoo; ``` \ No newline at end of file From df6877a1875a572d4fa01066453345bab7b48fac Mon Sep 17 00:00:00 2001 From: Sercan Altun Date: Fri, 1 Jan 2021 13:37:51 -0500 Subject: [PATCH 19/19] Applied suggested documentation changes --- Documentation/Async.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/Async.md b/Documentation/Async.md index 84d39e61b..84454e255 100644 --- a/Documentation/Async.md +++ b/Documentation/Async.md @@ -5,7 +5,7 @@ ## Table Of Contents * Introduction - * Async in DI + * `async` in DI * Example * Advanced * Static Memory Pools @@ -16,11 +16,11 @@ ## Introduction ### Async in DI -In dependency injection, the injector resolves dependencies of the target class only once, often after class is first created. In other words, injection is a one time process that does not track the injected dependencies to update them later on. If a dependency is not ready at the moment of injection; either binding would not resolve in case of optional bindings or fail completely throwing an error. +In dependency injection, the injector resolves dependencies of the target class only once, often after class is first created. In other words, injection is a one time process that does not track the injected dependencies to update them later on. If a dependency is not ready at the moment of injection, either the binding wouldn't resolve in case of optional bindings or would fail completely throwing an error. -This is creates a dilemma when implementing dependencies that are resolved asyncroniously. You can design around the DI limitations by carefully architecting your code so that the injection happens after async process is completed. This requires careful planning, increased complexity in setup. It is also prone to errors. +This creates a dilemma while implementing dependencies that are resolved asynchronous. You can design around the DI limitations by carefully designing your code so that the injection happens after the `async` process is completed. This requires careful planning, which leads to an increased complexity in the setup, and is also prone to errors. -Alternatively you can inject a intermediary object that tracks the result of the async operation. When you need to access the dependency, you can use this intermediary object to check if async task is completed and get the resulting object. With the experimental async support, we would like to provide ways to tackle this problem in Extenject. You can find Async extensions in **Plugins/Zenject/OptionalExtras/Async** folder. +Alternatively you can inject an intermediary object that tracks the result of the `async` operation. When you need to access the dependency, you can use this intermediary object to check if the `async` task is completed and get the resulting object. With the experimental `async` support, we would like to provide ways to tackle this problem in Extenject. You can find `async` extensions in the folder **Plugins/Zenject/OptionalExtras/Async**. ### Example @@ -69,13 +69,13 @@ public class Bar : IInitializable, IDisposable } ``` -Here we use `BindAsync().FromMethod()` to pass an async method delegate that waits for 100 ms and then returns a newly created `Foo` object. This method can be any other method with `async Task Method()` signature. `BindAsync` extension provides a separate binder for async operations. This binder is currently limited to a few `FromX()` providers. Features like Pooling and Factories are not supported at the moment. +Here we use `BindAsync().FromMethod()` to pass an `async` lambda that waits for 100 ms and then returns a newly created `Foo` instance. This method can be any other method with the signature `Task Method()`. *Note: the `async` keyword is an implementation detail and thus not part of the signature. The `BindAsync` extension method provides a separate binder for `async` operations. This binder is currently limited to a few `FromX()` providers. Features like Pooling and Factories are not supported at the moment. -With above binding `AsyncInject` object is added to the container. Since scope is set to `AsCached()` the operation will run only once and `AsyncInject` will keep the result. It is important to note that async operation will not start before this binding is resolved. If you want async operation to start immediately after installing use `NonLazy()` option. +With the above `AsyncInject` binding, the instance is added to the container. Since the scope is set to `AsCached()` the operation will run only once and `AsyncInject` will keep the result. It is important to note that `async` operations won't start before this binding is getting resolved. If you want `async` operation to start immediately after installing, use `NonLazy()` option. -Once injected to `Bar`, we can check whether the return value of the async operation is already available by `TryGetResult`. method. This method will return false if there is no result to return. If result is not ready yet, we can listen to `Completed` event to get the return value when async operation completes. +Once injected to `Bar`, we can check whether the return value of the `async` operation is already available by calling the `TryGetResult`. method. This method will return `false` if there is no result to return. If result is not ready yet, we can listen to the `Completed` event to get the return value when the `async` operation completes. -Alternatively we can use following methods to check result. +Alternatively we can use the following methods to check the results availability. ```csharp // Use HasResult to check if result exists if (_asyncFoo.HasResult)